From 432093c27eeaf97531864190799f16e2e8353cca Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 10 Mar 2023 22:00:29 +0000 Subject: [PATCH 01/75] update changelog with latest changes --- CHANGELOG.md | 2474 +++++++++++++++++++------------------------------- 1 file changed, 912 insertions(+), 1562 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d3e7baf511..c5592a47d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,1590 +6,985 @@ ## Bug Fixes +* typo in input for layer workflow +* change supported python version from 3.6.1 to 3.6.2, bump black ([#807](https://github.com/awslabs/aws-lambda-powertools-python/issues/807)) +* add entire ARN role instead of account and role name +* path to artefact +* lint files +* package_logger as const over logger instance +* remove f-strings that doesn't evaluate expr +* incorrect log keys, indentation, snippet consistency +* unzip the right artifact name +* use decorators, split cold start to ease reading +* remove apigw contract when using event-handler, apigw tracing +* remove unused json import +* removed ambiguous quotes from labels. +* download artefact into the layer dir +* parallel_run should fail when e2e tests fail +* bump aws-cdk version +* git-chlg docker image is broken +* repurpose test to cover parent loggers case +* mathc the name of the cdk synth from the build phase +* sight, yes a whitespace character breaks the build +* no need to cache npm since we only install cdk cli and don't have .lock files +* use addHandler over monkeypatch +* lock dependencies +* mypy errors +* **api_gateway:** fixed custom metrics issue when using debug mode ([#1827](https://github.com/awslabs/aws-lambda-powertools-python/issues/1827)) +* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) +* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) +* **apigateway:** remove indentation in debug_mode ([#987](https://github.com/awslabs/aws-lambda-powertools-python/issues/987)) +* **apigateway:** support nested router decorators ([#1709](https://github.com/awslabs/aws-lambda-powertools-python/issues/1709)) +* **apigateway:** support [@app](https://github.com/app).not_found() syntax & housekeeping ([#926](https://github.com/awslabs/aws-lambda-powertools-python/issues/926)) +* **apigateway:** allow list of HTTP methods in route method ([#838](https://github.com/awslabs/aws-lambda-powertools-python/issues/838)) +* **apigateway:** support dynamic routes with equal sign (RFC3986) ([#1737](https://github.com/awslabs/aws-lambda-powertools-python/issues/1737)) +* **apigateway:** update Response class to require status_code only ([#1560](https://github.com/awslabs/aws-lambda-powertools-python/issues/1560)) +* **batch:** report multiple failures ([#967](https://github.com/awslabs/aws-lambda-powertools-python/issues/967)) +* **batch:** docstring fix for success_handler() record parameter ([#1202](https://github.com/awslabs/aws-lambda-powertools-python/issues/1202)) +* **batch:** bugfix to clear exceptions between executions ([#1022](https://github.com/awslabs/aws-lambda-powertools-python/issues/1022)) +* **batch:** delete >10 messages in legacy sqs processor ([#818](https://github.com/awslabs/aws-lambda-powertools-python/issues/818)) +* **batch:** missing space in BatchProcessingError message ([#1201](https://github.com/awslabs/aws-lambda-powertools-python/issues/1201)) +* **ci:** ensure PR_AUTHOR is present for large_pr_split workflow +* **ci:** secret and OIDC inheritance in nested children workflow +* **ci:** temporarly remove pypi test deployment +* **ci:** new artifact path, sed gnu/linux syntax, and pypi test +* **ci:** workflow should use npx for CDK CLI +* **ci:** remove v2 suffix from SAR apps ([#1633](https://github.com/awslabs/aws-lambda-powertools-python/issues/1633)) +* **ci:** fix arm64 layer builds +* **ci:** integrate isort 5.0 with black to resolve conflicts * **ci:** bump CDK version - -## Documentation - -* **homepage:** revamp install UX & share how we build Lambda Layer ([#1978](https://github.com/awslabs/aws-lambda-powertools-python/issues/1978)) - -## Maintenance - -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.1 to 2.1.2 ([#1979](https://github.com/awslabs/aws-lambda-powertools-python/issues/1979)) -* **deps-dev:** bump pytest from 7.2.1 to 7.2.2 ([#1980](https://github.com/awslabs/aws-lambda-powertools-python/issues/1980)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.9 to 2.8.19.10 ([#1973](https://github.com/awslabs/aws-lambda-powertools-python/issues/1973)) -* **deps-dev:** bump hvac from 1.0.2 to 1.1.0 ([#1983](https://github.com/awslabs/aws-lambda-powertools-python/issues/1983)) -* **deps-dev:** bump mkdocs-material from 9.1.0 to 9.1.1 ([#1984](https://github.com/awslabs/aws-lambda-powertools-python/issues/1984)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.24 to 1.26.84 ([#1981](https://github.com/awslabs/aws-lambda-powertools-python/issues/1981)) -* **deps-dev:** bump mkdocs-material from 9.0.15 to 9.1.0 ([#1976](https://github.com/awslabs/aws-lambda-powertools-python/issues/1976)) -* **deps-dev:** bump cfn-lint from 0.67.0 to 0.74.0 ([#1974](https://github.com/awslabs/aws-lambda-powertools-python/issues/1974)) -* **deps-dev:** bump aws-cdk-lib from 2.66.1 to 2.67.0 ([#1977](https://github.com/awslabs/aws-lambda-powertools-python/issues/1977)) - - - -## [v2.9.1] - 2023-03-01 -## Bug Fixes - -* **idempotency:** revert dict mutation that impacted static_pk_value feature ([#1970](https://github.com/awslabs/aws-lambda-powertools-python/issues/1970)) - -## Documentation - -* **appsync:** add mutation example and infrastructure fix ([#1964](https://github.com/awslabs/aws-lambda-powertools-python/issues/1964)) -* **parameters:** fix typos and inconsistencies ([#1966](https://github.com/awslabs/aws-lambda-powertools-python/issues/1966)) - -## Maintenance - -* update project description -* update v2 layer ARN on documentation -* **ci:** disable pypi test due to maintenance mode -* **ci:** replace deprecated set-output commands ([#1957](https://github.com/awslabs/aws-lambda-powertools-python/issues/1957)) -* **deps:** bump fastjsonschema from 2.16.2 to 2.16.3 ([#1961](https://github.com/awslabs/aws-lambda-powertools-python/issues/1961)) -* **deps:** bump release-drafter/release-drafter from 5.22.0 to 5.23.0 ([#1947](https://github.com/awslabs/aws-lambda-powertools-python/issues/1947)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.0 to 2.1.1 ([#1958](https://github.com/awslabs/aws-lambda-powertools-python/issues/1958)) -* **deps-dev:** bump coverage from 7.2.0 to 7.2.1 ([#1963](https://github.com/awslabs/aws-lambda-powertools-python/issues/1963)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.8 to 2.8.19.9 ([#1960](https://github.com/awslabs/aws-lambda-powertools-python/issues/1960)) -* **deps-dev:** bump mkdocs-material from 9.0.14 to 9.0.15 ([#1959](https://github.com/awslabs/aws-lambda-powertools-python/issues/1959)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.55 to 1.26.80 ([#1967](https://github.com/awslabs/aws-lambda-powertools-python/issues/1967)) -* **deps-dev:** bump types-requests from 2.28.11.14 to 2.28.11.15 ([#1962](https://github.com/awslabs/aws-lambda-powertools-python/issues/1962)) -* **deps-dev:** bump aws-cdk-lib from 2.66.0 to 2.66.1 ([#1954](https://github.com/awslabs/aws-lambda-powertools-python/issues/1954)) -* **deps-dev:** bump coverage from 7.1.0 to 7.2.0 ([#1951](https://github.com/awslabs/aws-lambda-powertools-python/issues/1951)) -* **deps-dev:** bump mkdocs-material from 9.0.13 to 9.0.14 ([#1952](https://github.com/awslabs/aws-lambda-powertools-python/issues/1952)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.43 to 1.26.77 ([#1949](https://github.com/awslabs/aws-lambda-powertools-python/issues/1949)) -* **deps-dev:** bump types-requests from 2.28.11.13 to 2.28.11.14 ([#1946](https://github.com/awslabs/aws-lambda-powertools-python/issues/1946)) -* **deps-dev:** bump aws-cdk-lib from 2.65.0 to 2.66.0 ([#1948](https://github.com/awslabs/aws-lambda-powertools-python/issues/1948)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.7 to 2.8.19.8 ([#1945](https://github.com/awslabs/aws-lambda-powertools-python/issues/1945)) -* **parser:** add workaround to make API GW test button work ([#1971](https://github.com/awslabs/aws-lambda-powertools-python/issues/1971)) - - - -## [v2.9.0] - 2023-02-21 -## Bug Fixes - +* **ci:** build without buildkit +* **ci:** use docker driver on buildx +* **ci:** linting issues after flake8-blackbear,mypy upgrades +* **ci:** setup git client earlier to prevent dirty stash error +* **ci:** ignore v2 action for now +* **ci:** only run e2e tests on py 3.7 +* **ci:** reusable workflow secrets param +* **ci:** pass core fns to large pr workflow script +* **ci:** on_label permissioning model & workflow execution +* **ci:** increase permission to allow version sync back to repo +* **ci:** gracefully and successful exit changelog upon no changes +* **ci:** event resolution for on_label_added workflow +* **ci:** calculate parallel jobs based on infrastructure needs ([#1475](https://github.com/awslabs/aws-lambda-powertools-python/issues/1475)) +* **ci:** del flake8 direct dep over py3.6 conflicts and docs failure +* **ci:** remove utf-8 body in octokit body req +* **ci:** move from pip-tools to poetry on layers to fix conflicts +* **ci:** typo and bust gh actions cache +* **ci:** use poetry to resolve layer deps; pip for CDK +* **ci:** disable poetry venv for layer workflow as cdk ignores venv +* **ci:** add cdk v2 dep for layers workflow +* **ci:** move from pip-tools to poetry on layers reusable workflow +* **ci:** move from pip-tools to poetry on layers +* **ci:** temporarily disable changelog upon release +* **ci:** add explicit origin to fix release detached head +* **ci:** quote prBody GH expr on_opened_pr +* **ci:** changelog workflow must receive git tags too +* **ci:** add additional input to accurately describe intent on skip +* **ci:** job permissions +* **ci:** merged_pr add issues write access +* **ci:** add missing oidc token generation permission +* **ci:** disable merged_pr workflow +* **ci:** allow inherit secrets for reusable workflow +* **ci:** remove unused secret +* **ci:** label_related_issue unresolved var from history mixup +* **ci:** cond doesnt support two expr w/ env +* **ci:** only event is resolved in cond +* **ci:** remove unsupported env in workflow_call +* **ci:** unexpected symbol due to double quotes... +* **ci:** improve msg visibility on closed issues +* **ci:** keep layer version permission ([#1318](https://github.com/awslabs/aws-lambda-powertools-python/issues/1318)) +* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) +* **ci:** fetch all git info so we can check tags +* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) +* **ci:** remove additional quotes in PR action ([#1317](https://github.com/awslabs/aws-lambda-powertools-python/issues/1317)) +* **ci:** install poetry before calling setup/python with cache ([#1315](https://github.com/awslabs/aws-lambda-powertools-python/issues/1315)) +* **ci:** checkout project before validating related issue workflow +* **ci:** fixes typos and small issues on github scripts ([#1302](https://github.com/awslabs/aws-lambda-powertools-python/issues/1302)) +* **ci:** address conditional type on_merge +* **ci:** address pr title semantic not found logic +* **ci:** address gh-actions additional quotes; remove debug +* **ci:** regex group name for on_merge workflow +* **ci:** api docs path +* **ci:** move conditionals from yaml to code; leftover +* **ci:** move conditionals from yaml to code +* **ci:** accept core arg in label related issue workflow +* **ci:** match the name of the cdk synth from the build phase +* **ci:** regex to catch combination of related issues workflow +* **ci:** checkout project before validating related issue workflow +* **ci:** regex to catch combination of related issues workflow +* **ci:** use gh-pages env as official docs are wrong +* **ci:** pr label regex for special chars in title +* **ci:** scope e2e tests by python version +* **ci:** add auth to API HTTP Gateway and Lambda Function Url ([#1882](https://github.com/awslabs/aws-lambda-powertools-python/issues/1882)) +* **ci:** comment custom publish version checker * **ci:** upgraded cdk to match the version used on e2e tests +* **ci:** escape outputs as certain PRs can break GH Actions expressions +* **ci:** disable pre-commit hook download from version bump +* **ci:** skip sync master on docs hotfix +* **core:** fixes leftovers from rebase +* **data-classes:** underscore support in api gateway authorizer resource name ([#969](https://github.com/awslabs/aws-lambda-powertools-python/issues/969)) +* **data-classes:** Add missing SES fields and ([#1045](https://github.com/awslabs/aws-lambda-powertools-python/issues/1045)) +* **data-classes:** docstring typos and clean up ([#937](https://github.com/awslabs/aws-lambda-powertools-python/issues/937)) +* **deps:** bump dev dep mako version to address CVE-2022-40023 ([#1524](https://github.com/awslabs/aws-lambda-powertools-python/issues/1524)) +* **deps:** correct py36 marker for jmespath +* **deps:** Ignore boto3 changes until needed ([#1151](https://github.com/awslabs/aws-lambda-powertools-python/issues/1151)) +* **deps:** update jmespath marker to support 1.0 and py3.6 ([#1139](https://github.com/awslabs/aws-lambda-powertools-python/issues/1139)) +* **deps:** correct mypy types as dev dependency ([#1322](https://github.com/awslabs/aws-lambda-powertools-python/issues/1322)) +* **deps:** update build system to poetry-core ([#1651](https://github.com/awslabs/aws-lambda-powertools-python/issues/1651)) +* **deps-dev:** remove jmespath due to dev deps conflict ([#1148](https://github.com/awslabs/aws-lambda-powertools-python/issues/1148)) +* **docs:** remove Slack link ([#1210](https://github.com/awslabs/aws-lambda-powertools-python/issues/1210)) +* **event-handler:** body to empty string in CORS preflight (ALB non-compliant) ([#1249](https://github.com/awslabs/aws-lambda-powertools-python/issues/1249)) +* **event-sources:** Pass authorizer data to APIGatewayEventAuthorizer ([#897](https://github.com/awslabs/aws-lambda-powertools-python/issues/897)) +* **event-sources:** handle dynamodb null type as none, not bool ([#929](https://github.com/awslabs/aws-lambda-powertools-python/issues/929)) +* **event-sources:** handle claimsOverrideDetails set to null ([#878](https://github.com/awslabs/aws-lambda-powertools-python/issues/878)) +* **event_handler:** exception_handler to handle ServiceError exceptions ([#1160](https://github.com/awslabs/aws-lambda-powertools-python/issues/1160)) +* **event_handler:** Allow for event_source support ([#1159](https://github.com/awslabs/aws-lambda-powertools-python/issues/1159)) +* **event_handler:** docs snippets, high-level import CorsConfig ([#1019](https://github.com/awslabs/aws-lambda-powertools-python/issues/1019)) +* **event_handlers:** ImportError when importing Response from top-level event_handler ([#1388](https://github.com/awslabs/aws-lambda-powertools-python/issues/1388)) +* **event_handlers:** omit explicit None HTTP header values ([#1793](https://github.com/awslabs/aws-lambda-powertools-python/issues/1793)) +* **event_handlers:** handle lack of headers when using auto-compression feature ([#1325](https://github.com/awslabs/aws-lambda-powertools-python/issues/1325)) +* **event_sources:** add test for Function URL AuthZ ([#1421](https://github.com/awslabs/aws-lambda-powertools-python/issues/1421)) +* **event_sources:** implement Mapping protocol on DictWrapper for better interop with existing middlewares ([#1516](https://github.com/awslabs/aws-lambda-powertools-python/issues/1516)) * **feature-flags:** revert RuleAction Enum inheritance on str ([#1910](https://github.com/awslabs/aws-lambda-powertools-python/issues/1910)) +* **governance:** update label in names in issues +* **idempotency:** make idempotent_function decorator thread safe ([#1899](https://github.com/awslabs/aws-lambda-powertools-python/issues/1899)) +* **idempotency:** revert dict mutation that impacted static_pk_value feature ([#1970](https://github.com/awslabs/aws-lambda-powertools-python/issues/1970)) +* **idempotency:** idempotent_function should support standalone falsy values ([#1669](https://github.com/awslabs/aws-lambda-powertools-python/issues/1669)) +* **idempotency:** pass by value on idem key to guard inadvert mutations ([#1090](https://github.com/awslabs/aws-lambda-powertools-python/issues/1090)) +* **idempotency:** include decorated fn name in hash ([#869](https://github.com/awslabs/aws-lambda-powertools-python/issues/869)) +* **jmespath_util:** snappy as dev dep and typing example ([#1446](https://github.com/awslabs/aws-lambda-powertools-python/issues/1446)) +* **lambda-authorizer:** allow proxy resources path in arn ([#1051](https://github.com/awslabs/aws-lambda-powertools-python/issues/1051)) +* **license:** add MIT-0 license header ([#1871](https://github.com/awslabs/aws-lambda-powertools-python/issues/1871)) +* **license:** correction to MIT + MIT-0 (no proprietary anymore) ([#1883](https://github.com/awslabs/aws-lambda-powertools-python/issues/1883)) * **logger:** support exception and exception_name fields at any log level ([#1930](https://github.com/awslabs/aws-lambda-powertools-python/issues/1930)) +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) +* **logger:** support additional args for handlers when injecting lambda context ([#1276](https://github.com/awslabs/aws-lambda-powertools-python/issues/1276)) +* **logger:** clear_state regression on absent standard keys ([#1088](https://github.com/awslabs/aws-lambda-powertools-python/issues/1088)) +* **logger:** clear_state should keep custom key formats ([#1095](https://github.com/awslabs/aws-lambda-powertools-python/issues/1095)) +* **logger:** fix unknown attributes being ignored by mypy ([#1670](https://github.com/awslabs/aws-lambda-powertools-python/issues/1670)) +* **logger:** exclude source_logger in copy_config_to_registered_loggers ([#1001](https://github.com/awslabs/aws-lambda-powertools-python/issues/1001)) +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) +* **logger:** test generates logfile +* **logger:** ensure state is cleared for custom formatters ([#1072](https://github.com/awslabs/aws-lambda-powertools-python/issues/1072)) +* **logger-utils:** regression on exclude set leading to no formatter ([#1080](https://github.com/awslabs/aws-lambda-powertools-python/issues/1080)) * **metrics:** clarify no-metrics user warning ([#1935](https://github.com/awslabs/aws-lambda-powertools-python/issues/1935)) +* **metrics:** raise SchemaValidationError for >8 metric dimensions ([#1240](https://github.com/awslabs/aws-lambda-powertools-python/issues/1240)) +* **metrics:** ensure dimension_set is reused across instances (pointer) ([#1581](https://github.com/awslabs/aws-lambda-powertools-python/issues/1581)) +* **metrics:** explicit type to single_metric ctx manager ([#865](https://github.com/awslabs/aws-lambda-powertools-python/issues/865)) +* **metrics:** flush upon a single metric 100th data point ([#1046](https://github.com/awslabs/aws-lambda-powertools-python/issues/1046)) +* **middleware_factory:** ret type annotation for handler dec ([#1066](https://github.com/awslabs/aws-lambda-powertools-python/issues/1066)) +* **parameters:** get_secret correctly return SecretBinary value ([#1717](https://github.com/awslabs/aws-lambda-powertools-python/issues/1717)) +* **parameters:** appconfig internal _get docstrings ([#934](https://github.com/awslabs/aws-lambda-powertools-python/issues/934)) +* **parameters:** appconfig transform and return types ([#877](https://github.com/awslabs/aws-lambda-powertools-python/issues/877)) +* **parser:** body/QS can be null or omitted in apigw v1/v2 ([#820](https://github.com/awslabs/aws-lambda-powertools-python/issues/820)) +* **parser:** overload parse when using envelope ([#885](https://github.com/awslabs/aws-lambda-powertools-python/issues/885)) +* **parser:** kinesis sequence number is str, not int ([#907](https://github.com/awslabs/aws-lambda-powertools-python/issues/907)) +* **parser:** raise ValidationError when SNS->SQS keys are intentionally missing ([#1299](https://github.com/awslabs/aws-lambda-powertools-python/issues/1299)) +* **parser:** mypy support for payload type override as models ([#883](https://github.com/awslabs/aws-lambda-powertools-python/issues/883)) +* **parser:** loose validation on SNS fields to support FIFO ([#1606](https://github.com/awslabs/aws-lambda-powertools-python/issues/1606)) +* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) +* **parser:** S3Model Object Deleted omits size and eTag attr ([#1638](https://github.com/awslabs/aws-lambda-powertools-python/issues/1638)) +* **tests:** make sure multiple e2e tests run concurrently ([#1861](https://github.com/awslabs/aws-lambda-powertools-python/issues/1861)) +* **tests:** make logs fetching more robust ([#1878](https://github.com/awslabs/aws-lambda-powertools-python/issues/1878)) +* **tests:** remove custom workers +* **tracer:** add warm start annotation (ColdStart=False) ([#851](https://github.com/awslabs/aws-lambda-powertools-python/issues/851)) +* **typing:** level arg in copy_config_to_registered_loggers ([#1534](https://github.com/awslabs/aws-lambda-powertools-python/issues/1534)) +* **typing:** fix mypy error +* **warning:** future distutils deprecation ([#921](https://github.com/awslabs/aws-lambda-powertools-python/issues/921)) -## Documentation - -* **event_handlers:** Fix REST API - HTTP Methods documentation ([#1936](https://github.com/awslabs/aws-lambda-powertools-python/issues/1936)) -* **home:** update powertools definition -* **we-made-this:** add CI/CD using Feature Flags video ([#1940](https://github.com/awslabs/aws-lambda-powertools-python/issues/1940)) -* **we-made-this:** add Feature Flags post ([#1939](https://github.com/awslabs/aws-lambda-powertools-python/issues/1939)) - -## Features +## Code Refactoring -* **batch:** add support to SQS FIFO queues (SqsFifoPartialProcessor) ([#1934](https://github.com/awslabs/aws-lambda-powertools-python/issues/1934)) +* rename to remove_custom_keys +* rename to clear_state +* **apigateway:** Add BaseRouter and duplicate route check ([#757](https://github.com/awslabs/aws-lambda-powertools-python/issues/757)) +* **apigateway:** remove POWERTOOLS_EVENT_HANDLER_DEBUG env var ([#1620](https://github.com/awslabs/aws-lambda-powertools-python/issues/1620)) +* **batch:** remove legacy sqs_batch_processor ([#1492](https://github.com/awslabs/aws-lambda-powertools-python/issues/1492)) +* **e2e:** make table name dynamic +* **e2e:** fix idempotency typing -## Maintenance - -* update v2 layer ARN on documentation -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.5 to 2.1.0 ([#1943](https://github.com/awslabs/aws-lambda-powertools-python/issues/1943)) -* **deps:** bump pydantic from 1.10.4 to 1.10.5 ([#1931](https://github.com/awslabs/aws-lambda-powertools-python/issues/1931)) -* **deps-dev:** bump mkdocs-material from 9.0.12 to 9.0.13 ([#1944](https://github.com/awslabs/aws-lambda-powertools-python/issues/1944)) -* **deps-dev:** bump aws-cdk-lib from 2.64.0 to 2.65.0 ([#1938](https://github.com/awslabs/aws-lambda-powertools-python/issues/1938)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.6 to 2.8.19.7 ([#1932](https://github.com/awslabs/aws-lambda-powertools-python/issues/1932)) -* **deps-dev:** bump types-requests from 2.28.11.12 to 2.28.11.13 ([#1933](https://github.com/awslabs/aws-lambda-powertools-python/issues/1933)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.26.63 to 1.26.71 ([#1928](https://github.com/awslabs/aws-lambda-powertools-python/issues/1928)) -* **deps-dev:** bump flake8-bugbear from 23.1.20 to 23.2.13 ([#1924](https://github.com/awslabs/aws-lambda-powertools-python/issues/1924)) -* **deps-dev:** bump mypy-boto3-appconfigdata from 1.26.0.post1 to 1.26.70 ([#1925](https://github.com/awslabs/aws-lambda-powertools-python/issues/1925)) - - - -## [v2.8.0] - 2023-02-10 -## Bug Fixes - -* **idempotency:** make idempotent_function decorator thread safe ([#1899](https://github.com/awslabs/aws-lambda-powertools-python/issues/1899)) - -## Documentation +## Documentation +* consistency around admonitions and snippets ([#919](https://github.com/awslabs/aws-lambda-powertools-python/issues/919)) +* disable search blur in non-prod env +* add amplify-cli instructions for public layer ([#754](https://github.com/awslabs/aws-lambda-powertools-python/issues/754)) +* improve public lambda layer wording, clipboard buttons ([#762](https://github.com/awslabs/aws-lambda-powertools-python/issues/762)) +* fix anchor +* fix indentation of SAM snippets in install section ([#778](https://github.com/awslabs/aws-lambda-powertools-python/issues/778)) +* external reference to cloudformation custom resource helper ([#914](https://github.com/awslabs/aws-lambda-powertools-python/issues/914)) +* rename to tutorial given the size +* updated Lambda Layers definition & limitations. ([#775](https://github.com/awslabs/aws-lambda-powertools-python/issues/775)) +* Idiomatic tenet updated to Progressive +* fix syntax errors and line highlights ([#1004](https://github.com/awslabs/aws-lambda-powertools-python/issues/1004)) +* update Lambda Layers version +* add better BDD coments +* Added GraphQL Sample API to Examples section of README.md ([#930](https://github.com/awslabs/aws-lambda-powertools-python/issues/930)) +* project name consistency +* rename quickstart to tutorial in readme +* use higher contrast font +* use higher contrast font ([#822](https://github.com/awslabs/aws-lambda-powertools-python/issues/822)) +* add final consideration section +* add new public Slack invite +* **api-gateway:** add support for new router feature ([#767](https://github.com/awslabs/aws-lambda-powertools-python/issues/767)) +* **apigateway:** add all resolvers in testing your code section for accuracy ([#1688](https://github.com/awslabs/aws-lambda-powertools-python/issues/1688)) +* **apigateway:** add new not_found feature ([#915](https://github.com/awslabs/aws-lambda-powertools-python/issues/915)) +* **apigateway:** re-add sample layout, add considerations ([#826](https://github.com/awslabs/aws-lambda-powertools-python/issues/826)) +* **apigateway:** removes duplicate admonition ([#1426](https://github.com/awslabs/aws-lambda-powertools-python/issues/1426)) +* **apigateway:** fix sample layout provided ([#864](https://github.com/awslabs/aws-lambda-powertools-python/issues/864)) +* **appsync:** fix users.py typo to locations [#830](https://github.com/awslabs/aws-lambda-powertools-python/issues/830) +* **appsync:** add mutation example and infrastructure fix ([#1964](https://github.com/awslabs/aws-lambda-powertools-python/issues/1964)) +* **appsync:** add new router feature ([#821](https://github.com/awslabs/aws-lambda-powertools-python/issues/821)) +* **appsync:** fix typo +* **batch:** snippet typo on batch processed messages iteration ([#951](https://github.com/awslabs/aws-lambda-powertools-python/issues/951)) +* **batch:** remove leftover from legacy +* **batch:** document the new lambda context feature +* **batch:** remove legacy reference to sqs processor +* **batch:** fix typo in context manager keyword ([#938](https://github.com/awslabs/aws-lambda-powertools-python/issues/938)) +* **community:** fix social handlers for Ran ([#1654](https://github.com/awslabs/aws-lambda-powertools-python/issues/1654)) +* **community:** fix twitch parent domain for embedded video +* **contributing:** operational excellence pause * **engine:** re-enable clipboard button for code snippets +* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) +* **event-handler:** improve testing section for graphql ([#996](https://github.com/awslabs/aws-lambda-powertools-python/issues/996)) +* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) +* **event-source:** fix incorrect method in example CloudWatch Logs ([#1857](https://github.com/awslabs/aws-lambda-powertools-python/issues/1857)) +* **event_handlers:** Fix REST API - HTTP Methods documentation ([#1936](https://github.com/awslabs/aws-lambda-powertools-python/issues/1936)) +* **examples:** linting unnecessary whitespace +* **examples:** enforce and fix all mypy errors ([#1393](https://github.com/awslabs/aws-lambda-powertools-python/issues/1393)) +* **governance:** allow community to suggest feature content ([#1593](https://github.com/awslabs/aws-lambda-powertools-python/issues/1593)) +* **governance:** new form to allow customers self-nominate as public reference ([#1589](https://github.com/awslabs/aws-lambda-powertools-python/issues/1589)) +* **governance:** add security doc to the root +* **governance:** typos on PR template fixes [#1314](https://github.com/awslabs/aws-lambda-powertools-python/issues/1314) +* **governance:** link roadmap and maintainers doc +* **graphql:** snippets split, improved, and lint ([#1287](https://github.com/awslabs/aws-lambda-powertools-python/issues/1287)) +* **home:** update powertools definition +* **home:** add discord invitation link ([#1471](https://github.com/awslabs/aws-lambda-powertools-python/issues/1471)) +* **home:** fix discord syntax and add Discord badge +* **homepage:** include .NET powertools +* **homepage:** update default value for `POWERTOOLS_DEV` ([#1695](https://github.com/awslabs/aws-lambda-powertools-python/issues/1695)) +* **homepage:** add banner for end-of-support v1 ([#1879](https://github.com/awslabs/aws-lambda-powertools-python/issues/1879)) * **homepage:** Replace poetry command to add group parameter ([#1917](https://github.com/awslabs/aws-lambda-powertools-python/issues/1917)) +* **homepage:** link to typescript version ([#950](https://github.com/awslabs/aws-lambda-powertools-python/issues/950)) * **homepage:** set url for end-of-support in announce block ([#1893](https://github.com/awslabs/aws-lambda-powertools-python/issues/1893)) +* **homepage:** remove v1 layer limitation on pydantic not being included +* **homepage:** note about v2 version +* **homepage:** emphasize additional powertools languages ([#1292](https://github.com/awslabs/aws-lambda-powertools-python/issues/1292)) +* **homepage:** remove 3.6 and add hero image +* **homepage:** introduce POWERTOOLS_DEV env var ([#1569](https://github.com/awslabs/aws-lambda-powertools-python/issues/1569)) +* **homepage:** revamp install UX & share how we build Lambda Layer ([#1978](https://github.com/awslabs/aws-lambda-powertools-python/issues/1978)) +* **homepage:** auto-update Layer ARN on every release ([#1610](https://github.com/awslabs/aws-lambda-powertools-python/issues/1610)) +* **homepage:** add Pulumi code example ([#1652](https://github.com/awslabs/aws-lambda-powertools-python/issues/1652)) +* **idempotency:** fix register_lambda_context order ([#1747](https://github.com/awslabs/aws-lambda-powertools-python/issues/1747)) +* **idempotency:** add missing Lambda Context; note on thread-safe ([#1732](https://github.com/awslabs/aws-lambda-powertools-python/issues/1732)) +* **idempotency:** add support for DynamoDB composite keys ([#808](https://github.com/awslabs/aws-lambda-powertools-python/issues/808)) +* **idempotency:** "persisntence" typo ([#1596](https://github.com/awslabs/aws-lambda-powertools-python/issues/1596)) * **idempotency:** add IAM permissions section ([#1902](https://github.com/awslabs/aws-lambda-powertools-python/issues/1902)) +* **idempotency:** fix, improve, and increase visibility for batch integration ([#1776](https://github.com/awslabs/aws-lambda-powertools-python/issues/1776)) +* **index:** fold support us banner +* **index:** add quotes to pip for zsh customers +* **install:** new lambda layer for 1.24.0 release +* **install:** instructions to reduce pydantic package size ([#1077](https://github.com/awslabs/aws-lambda-powertools-python/issues/1077)) +* **install:** address early v2 feedback on installation and project support +* **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) +* **lambda_layer:** fix CDK layer syntax +* **layer:** upgrade to 1.25.10 +* **layer:** upgrade to 1.26.7 +* **layer:** upgrade to 1.27.0 +* **layer:** bump Lambda Layer to version 6 +* **layer:** upgrade to 1.25.9 +* **layer:** remove link from clipboard button ([#1135](https://github.com/awslabs/aws-lambda-powertools-python/issues/1135)) +* **layer:** update to 1.24.2 +* **layer:** update to 1.25.7 +* **layer:** update to 1.25.6; cosmetic changes +* **layer:** bump to 1.25.5 +* **layer:** upgrade to 1.27.0 +* **layer:** update to 1.24.1 +* **layer:** update to 1.25.3 +* **layer:** update to 1.25.1 +* **layer:** upgrade to 1.28.0 (v33) +* **lint:** add markdownlint rules and automation ([#1256](https://github.com/awslabs/aws-lambda-powertools-python/issues/1256)) +* **logger:** fix typo. ([#1587](https://github.com/awslabs/aws-lambda-powertools-python/issues/1587)) +* **logger:** Add warning of uncaught exceptions ([#1826](https://github.com/awslabs/aws-lambda-powertools-python/issues/1826)) +* **logger:** document enriching logs with logrecord attributes ([#1271](https://github.com/awslabs/aws-lambda-powertools-python/issues/1271)) +* **logger:** fix incorrect field names in example structured logs ([#1830](https://github.com/awslabs/aws-lambda-powertools-python/issues/1830)) +* **logger:** snippets split, improved, and lint ([#1262](https://github.com/awslabs/aws-lambda-powertools-python/issues/1262)) +* **logger:** update uncaught exception message value +* **maintainers:** initial maintainers playbook ([#1222](https://github.com/awslabs/aws-lambda-powertools-python/issues/1222)) * **metrics:** remove reduntant wording before release * **metrics:** fix syntax highlighting for new default_dimensions +* **metrics:** snippets split, improved, and lint +* **metrics:** snippets split, improved, and lint ([#1272](https://github.com/awslabs/aws-lambda-powertools-python/issues/1272)) +* **metrics:** keep it consistent with other sections, update metric names +* **middleware-factory:** snippets split, improved, and lint ([#1451](https://github.com/awslabs/aws-lambda-powertools-python/issues/1451)) +* **multiple:** fix highlighting after new isort/black integration +* **nav:** make REST and GraphQL event handlers more explicit ([#959](https://github.com/awslabs/aws-lambda-powertools-python/issues/959)) +* **parameters:** fix typos and inconsistencies ([#1966](https://github.com/awslabs/aws-lambda-powertools-python/issues/1966)) +* **parameters:** snippets split, improved, and lint ([#1564](https://github.com/awslabs/aws-lambda-powertools-python/issues/1564)) +* **parameters:** add testing your code section ([#1017](https://github.com/awslabs/aws-lambda-powertools-python/issues/1017)) +* **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) +* **parser:** add JSON string field extension example ([#1526](https://github.com/awslabs/aws-lambda-powertools-python/issues/1526)) +* **parser:** APIGatewayProxyEvent to APIGatewayProxyEventModel ([#1061](https://github.com/awslabs/aws-lambda-powertools-python/issues/1061)) +* **plugin:** add mermaid to create diagram as code ([#1070](https://github.com/awslabs/aws-lambda-powertools-python/issues/1070)) +* **quickstart:** expand on intro line +* **quickstart:** sentence fragmentation, tidy up +* **quickstart:** tidy requirements up +* **quickstart:** add sub-sections, fix highlight & code +* **quickstart:** same process for Logger +* **quickstart:** make section agnostic to json lib +* **readme:** add lambda layer latest version badge +* **roadmap:** add new roadmap section ([#1204](https://github.com/awslabs/aws-lambda-powertools-python/issues/1204)) +* **roadmap:** use pinned pause issue instead +* **roadmap:** include observability provider and lambda layer themes before v2 +* **roadmap:** refresh roadmap post-v2 launch +* **streaming:** fix leftover newline +* **tenets:** update Idiomatic tenet to Progressive ([#823](https://github.com/awslabs/aws-lambda-powertools-python/issues/823)) +* **tenets:** make core, non-core more explicit +* **theme:** upgrade mkdocs-material to 8.x ([#1002](https://github.com/awslabs/aws-lambda-powertools-python/issues/1002)) +* **tracer:** snippets split, improved, and lint ([#1261](https://github.com/awslabs/aws-lambda-powertools-python/issues/1261)) +* **tracer:** update ServiceLens image w/ API GW, copywriting +* **tracer:** new ignore_endpoint feature ([#931](https://github.com/awslabs/aws-lambda-powertools-python/issues/931)) +* **tracer:** add annotation, metadata, and image +* **tracer:** add note on why X-Ray SDK over ADOT closes [#1675](https://github.com/awslabs/aws-lambda-powertools-python/issues/1675) +* **tracer:** add initial image, requirements +* **tracer:** warning to note on local traces +* **tracer:** split and lint code snippets ([#1260](https://github.com/awslabs/aws-lambda-powertools-python/issues/1260)) +* **tutorial:** fix path to images ([#963](https://github.com/awslabs/aws-lambda-powertools-python/issues/963)) +* **tutorial:** fix broken internal links ([#1000](https://github.com/awslabs/aws-lambda-powertools-python/issues/1000)) +* **typing:** snippets split, improved, and lint ([#1465](https://github.com/awslabs/aws-lambda-powertools-python/issues/1465)) +* **upgrade_guide:** add latest changes and quick summary ([#1623](https://github.com/awslabs/aws-lambda-powertools-python/issues/1623)) +* **v2:** document optional dependencies and local dev ([#1574](https://github.com/awslabs/aws-lambda-powertools-python/issues/1574)) +* **validation:** snippets split, improved, and lint ([#1449](https://github.com/awslabs/aws-lambda-powertools-python/issues/1449)) +* **validation:** fix broken link; enrich built-in jmespath links ([#1777](https://github.com/awslabs/aws-lambda-powertools-python/issues/1777)) +* **we-made-this:** new community content section ([#1650](https://github.com/awslabs/aws-lambda-powertools-python/issues/1650)) +* **we-made-this:** add Feature Flags post ([#1939](https://github.com/awslabs/aws-lambda-powertools-python/issues/1939)) +* **we-made-this:** add CI/CD using Feature Flags video ([#1940](https://github.com/awslabs/aws-lambda-powertools-python/issues/1940)) ## Features +* **apigateway:** multiple exceptions in exception_handler ([#1707](https://github.com/awslabs/aws-lambda-powertools-python/issues/1707)) +* **apigateway:** add Router to allow large routing composition ([#645](https://github.com/awslabs/aws-lambda-powertools-python/issues/645)) +* **apigateway:** ignore trailing slashes in routes (APIGatewayRestResolver) ([#1609](https://github.com/awslabs/aws-lambda-powertools-python/issues/1609)) +* **apigateway:** access parent api resolver from router ([#842](https://github.com/awslabs/aws-lambda-powertools-python/issues/842)) +* **apigateway:** add exception_handler support ([#898](https://github.com/awslabs/aws-lambda-powertools-python/issues/898)) +* **appsync:** add Router to allow large resolver composition ([#776](https://github.com/awslabs/aws-lambda-powertools-python/issues/776)) * **batch:** add async_batch_processor for concurrent processing ([#1724](https://github.com/awslabs/aws-lambda-powertools-python/issues/1724)) +* **batch:** new BatchProcessor for SQS, DynamoDB, Kinesis ([#886](https://github.com/awslabs/aws-lambda-powertools-python/issues/886)) +* **batch:** add support to SQS FIFO queues (SqsFifoPartialProcessor) ([#1934](https://github.com/awslabs/aws-lambda-powertools-python/issues/1934)) +* **batch:** inject lambda_context if record handler signature accepts it ([#1561](https://github.com/awslabs/aws-lambda-powertools-python/issues/1561)) +* **ci:** release docs as alpha when doing a pre-release ([#1624](https://github.com/awslabs/aws-lambda-powertools-python/issues/1624)) +* **ci:** auto-notify & close issues on release +* **ci:** create reusable changelog generation +* **ci:** include changelog generation on docs build +* **ci:** create reusable changelog generation ([#1418](https://github.com/awslabs/aws-lambda-powertools-python/issues/1418)) +* **ci:** add actionlint in pre-commit hook +* **data-classes:** add KafkaEvent and KafkaEventRecord ([#1485](https://github.com/awslabs/aws-lambda-powertools-python/issues/1485)) +* **data-classes:** replace AttributeValue in DynamoDBStreamEvent with deserialized Python values ([#1619](https://github.com/awslabs/aws-lambda-powertools-python/issues/1619)) +* **data-classes:** ActiveMQ and RabbitMQ support ([#770](https://github.com/awslabs/aws-lambda-powertools-python/issues/770)) +* **data_classes:** add KinesisFirehoseEvent ([#1540](https://github.com/awslabs/aws-lambda-powertools-python/issues/1540)) +* **event-handler:** new resolvers to fix current_event typing ([#978](https://github.com/awslabs/aws-lambda-powertools-python/issues/978)) +* **event-handler:** context support to share data between routers ([#1567](https://github.com/awslabs/aws-lambda-powertools-python/issues/1567)) +* **event-sources:** cache parsed json in data class ([#909](https://github.com/awslabs/aws-lambda-powertools-python/issues/909)) +* **event_handler:** improved support for headers and cookies in v2 ([#1455](https://github.com/awslabs/aws-lambda-powertools-python/issues/1455)) +* **event_handler:** add cookies as 1st class citizen in v2 ([#1487](https://github.com/awslabs/aws-lambda-powertools-python/issues/1487)) +* **event_handlers:** Add support for Lambda Function URLs ([#1408](https://github.com/awslabs/aws-lambda-powertools-python/issues/1408)) +* **event_sources:** extract CloudWatch Logs in Kinesis streams ([#1710](https://github.com/awslabs/aws-lambda-powertools-python/issues/1710)) +* **event_sources:** add CloudWatch dashboard custom widget event ([#1474](https://github.com/awslabs/aws-lambda-powertools-python/issues/1474)) +* **feature_flags:** Add Time based feature flags actions ([#1846](https://github.com/awslabs/aws-lambda-powertools-python/issues/1846)) +* **feature_flags:** support beyond boolean values (JSON values) ([#804](https://github.com/awslabs/aws-lambda-powertools-python/issues/804)) +* **idempotency:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1535](https://github.com/awslabs/aws-lambda-powertools-python/issues/1535)) +* **idempotency:** support dataclasses & pydantic models payloads ([#908](https://github.com/awslabs/aws-lambda-powertools-python/issues/908)) +* **idempotency:** handle lambda timeout scenarios for INPROGRESS records ([#1387](https://github.com/awslabs/aws-lambda-powertools-python/issues/1387)) +* **layer:** publish SAR v2 via Github actions ([#1585](https://github.com/awslabs/aws-lambda-powertools-python/issues/1585)) +* **layers:** add layer balancer script ([#1643](https://github.com/awslabs/aws-lambda-powertools-python/issues/1643)) +* **layers:** add support for publishing v2 layer ([#1558](https://github.com/awslabs/aws-lambda-powertools-python/issues/1558)) +* **logger:** clone powertools logger config to any Python logger ([#927](https://github.com/awslabs/aws-lambda-powertools-python/issues/927)) +* **logger:** log uncaught exceptions via system's exception hook ([#1727](https://github.com/awslabs/aws-lambda-powertools-python/issues/1727)) +* **logger:** allow handler with custom kwargs signature ([#913](https://github.com/awslabs/aws-lambda-powertools-python/issues/913)) +* **logger:** accept arbitrary keyword=value for ephemeral metadata ([#1658](https://github.com/awslabs/aws-lambda-powertools-python/issues/1658)) +* **logger:** add use_rfc3339 and auto-complete formatter opts in Logger ([#1662](https://github.com/awslabs/aws-lambda-powertools-python/issues/1662)) +* **logger:** add ALB correlation ID support ([#816](https://github.com/awslabs/aws-lambda-powertools-python/issues/816)) +* **logger:** unwrap event from common models if asked to log ([#1778](https://github.com/awslabs/aws-lambda-powertools-python/issues/1778)) +* **logger:** pretty-print JSON when POWERTOOLS_DEV is set ([#1548](https://github.com/awslabs/aws-lambda-powertools-python/issues/1548)) +* **logger:** include logger name attribute when copy_config_to_registered_logger is used ([#1568](https://github.com/awslabs/aws-lambda-powertools-python/issues/1568)) +* **logger:** introduce POWERTOOLS_DEBUG for internal debugging ([#1572](https://github.com/awslabs/aws-lambda-powertools-python/issues/1572)) +* **logger:** support use_datetime_directive for timestamps ([#920](https://github.com/awslabs/aws-lambda-powertools-python/issues/920)) +* **logger:** log_event support event data classes (e.g. S3Event) ([#984](https://github.com/awslabs/aws-lambda-powertools-python/issues/984)) +* **logger:** add option to clear state per invocation +* **metrics:** update max user-defined dimensions from 9 to 29 ([#1417](https://github.com/awslabs/aws-lambda-powertools-python/issues/1417)) +* **metrics:** add EphemeralMetrics as a non-singleton option ([#1676](https://github.com/awslabs/aws-lambda-powertools-python/issues/1676)) * **metrics:** add default_dimensions to single_metric ([#1880](https://github.com/awslabs/aws-lambda-powertools-python/issues/1880)) +* **mypy:** complete mypy support for the entire codebase ([#943](https://github.com/awslabs/aws-lambda-powertools-python/issues/943)) +* **parameters:** add get_parameters_by_name for SSM params in distinct paths ([#1678](https://github.com/awslabs/aws-lambda-powertools-python/issues/1678)) +* **parameters:** accept boto3_client to support private endpoints and ease testing ([#1096](https://github.com/awslabs/aws-lambda-powertools-python/issues/1096)) +* **parameters:** migrate AppConfig to new APIs due to API deprecation ([#1553](https://github.com/awslabs/aws-lambda-powertools-python/issues/1553)) +* **parameters:** add clear_cache method for providers ([#1194](https://github.com/awslabs/aws-lambda-powertools-python/issues/1194)) +* **parser:** add KinesisFirehoseModel ([#1556](https://github.com/awslabs/aws-lambda-powertools-python/issues/1556)) +* **parser:** export Pydantic.errors through escape hatch ([#1728](https://github.com/awslabs/aws-lambda-powertools-python/issues/1728)) +* **parser:** add support for Lambda Function URL ([#1442](https://github.com/awslabs/aws-lambda-powertools-python/issues/1442)) +* **parser:** extract CloudWatch Logs in Kinesis streams ([#1726](https://github.com/awslabs/aws-lambda-powertools-python/issues/1726)) +* **parser:** add KafkaMskEventModel and KafkaSelfManagedEventModel ([#1499](https://github.com/awslabs/aws-lambda-powertools-python/issues/1499)) +* **streaming:** add new s3 streaming utility ([#1719](https://github.com/awslabs/aws-lambda-powertools-python/issues/1719)) +* **tracer:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1486](https://github.com/awslabs/aws-lambda-powertools-python/issues/1486)) +* **tracer:** ignore tracing for certain hostname(s) or url(s) ([#910](https://github.com/awslabs/aws-lambda-powertools-python/issues/910)) +* **tracer:** add service annotation when service is set ([#861](https://github.com/awslabs/aws-lambda-powertools-python/issues/861)) ## Maintenance * update v2 layer ARN on documentation -* **deps:** bump docker/setup-buildx-action from 2.4.0 to 2.4.1 ([#1903](https://github.com/awslabs/aws-lambda-powertools-python/issues/1903)) -* **deps-dev:** bump aws-cdk-lib from 2.63.0 to 2.63.2 ([#1904](https://github.com/awslabs/aws-lambda-powertools-python/issues/1904)) -* **deps-dev:** bump black from 22.12.0 to 23.1.0 ([#1886](https://github.com/awslabs/aws-lambda-powertools-python/issues/1886)) -* **deps-dev:** bump types-requests from 2.28.11.8 to 2.28.11.12 ([#1906](https://github.com/awslabs/aws-lambda-powertools-python/issues/1906)) -* **deps-dev:** bump pytest-xdist from 3.1.0 to 3.2.0 ([#1905](https://github.com/awslabs/aws-lambda-powertools-python/issues/1905)) -* **deps-dev:** bump aws-cdk-lib from 2.63.2 to 2.64.0 ([#1918](https://github.com/awslabs/aws-lambda-powertools-python/issues/1918)) -* **deps-dev:** bump mkdocs-material from 9.0.11 to 9.0.12 ([#1919](https://github.com/awslabs/aws-lambda-powertools-python/issues/1919)) -* **deps-dev:** bump mkdocs-material from 9.0.10 to 9.0.11 ([#1896](https://github.com/awslabs/aws-lambda-powertools-python/issues/1896)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.26.0.post1 to 1.26.63 ([#1895](https://github.com/awslabs/aws-lambda-powertools-python/issues/1895)) -* **deps-dev:** bump mypy-boto3-s3 from 1.26.58 to 1.26.62 ([#1889](https://github.com/awslabs/aws-lambda-powertools-python/issues/1889)) -* **deps-dev:** bump mkdocs-material from 9.0.9 to 9.0.10 ([#1888](https://github.com/awslabs/aws-lambda-powertools-python/issues/1888)) -* **deps-dev:** bump aws-cdk-lib from 2.62.2 to 2.63.0 ([#1887](https://github.com/awslabs/aws-lambda-powertools-python/issues/1887)) -* **maintainers:** fix release workflow rename -* **pypi:** add new links to Pypi package homepage ([#1912](https://github.com/awslabs/aws-lambda-powertools-python/issues/1912)) - - - -## [v2.7.1] - 2023-02-01 -## Bug Fixes - -* parallel_run should fail when e2e tests fail -* bump aws-cdk version -* **ci:** scope e2e tests by python version -* **ci:** add auth to API HTTP Gateway and Lambda Function Url ([#1882](https://github.com/awslabs/aws-lambda-powertools-python/issues/1882)) -* **license:** correction to MIT + MIT-0 (no proprietary anymore) ([#1883](https://github.com/awslabs/aws-lambda-powertools-python/issues/1883)) -* **license:** add MIT-0 license header ([#1871](https://github.com/awslabs/aws-lambda-powertools-python/issues/1871)) -* **tests:** make logs fetching more robust ([#1878](https://github.com/awslabs/aws-lambda-powertools-python/issues/1878)) -* **tests:** remove custom workers -* **tests:** make sure multiple e2e tests run concurrently ([#1861](https://github.com/awslabs/aws-lambda-powertools-python/issues/1861)) - -## Documentation - -* **event-source:** fix incorrect method in example CloudWatch Logs ([#1857](https://github.com/awslabs/aws-lambda-powertools-python/issues/1857)) -* **homepage:** add banner for end-of-support v1 ([#1879](https://github.com/awslabs/aws-lambda-powertools-python/issues/1879)) -* **parameters:** snippets split, improved, and lint ([#1564](https://github.com/awslabs/aws-lambda-powertools-python/issues/1564)) - -## Maintenance - +* remove Lambda Layer version tag +* conditional to publish docs only +* conditional to publish docs only attempt 2 +* conditional to publish docs only attempt 3 +* bump to 1.22.0 * update v2 layer ARN on documentation -* **deps:** bump docker/setup-buildx-action from 2.0.0 to 2.4.0 ([#1873](https://github.com/awslabs/aws-lambda-powertools-python/issues/1873)) -* **deps:** bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 ([#1855](https://github.com/awslabs/aws-lambda-powertools-python/issues/1855)) -* **deps-dev:** bump mypy-boto3-s3 from 1.26.0.post1 to 1.26.58 ([#1868](https://github.com/awslabs/aws-lambda-powertools-python/issues/1868)) -* **deps-dev:** bump isort from 5.11.4 to 5.11.5 ([#1875](https://github.com/awslabs/aws-lambda-powertools-python/issues/1875)) -* **deps-dev:** bump aws-cdk-lib from 2.62.1 to 2.62.2 ([#1869](https://github.com/awslabs/aws-lambda-powertools-python/issues/1869)) -* **deps-dev:** bump mkdocs-material from 9.0.6 to 9.0.8 ([#1874](https://github.com/awslabs/aws-lambda-powertools-python/issues/1874)) -* **deps-dev:** bump aws-cdk-lib from 2.62.0 to 2.62.1 ([#1866](https://github.com/awslabs/aws-lambda-powertools-python/issues/1866)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.35.post1 to 1.26.57 ([#1865](https://github.com/awslabs/aws-lambda-powertools-python/issues/1865)) -* **deps-dev:** bump coverage from 7.0.5 to 7.1.0 ([#1862](https://github.com/awslabs/aws-lambda-powertools-python/issues/1862)) -* **deps-dev:** bump aws-cdk-lib from 2.61.1 to 2.62.0 ([#1863](https://github.com/awslabs/aws-lambda-powertools-python/issues/1863)) -* **deps-dev:** bump flake8-bugbear from 22.12.6 to 23.1.20 ([#1854](https://github.com/awslabs/aws-lambda-powertools-python/issues/1854)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.49 to 1.26.55 ([#1856](https://github.com/awslabs/aws-lambda-powertools-python/issues/1856)) - -## Reverts -* fix(tests): remove custom workers - - - -## [v2.7.0] - 2023-01-24 -## Bug Fixes - -* git-chlg docker image is broken - -## Features - -* **feature_flags:** Add Time based feature flags actions ([#1846](https://github.com/awslabs/aws-lambda-powertools-python/issues/1846)) - -## Maintenance - +* correct pr label order +* minor housekeeping before release ([#912](https://github.com/awslabs/aws-lambda-powertools-python/issues/912)) +* bump to 1.23.0 +* bump to 1.24.0 +* apigw test event wrongly set with base64 * update v2 layer ARN on documentation -* **deps:** bump peaceiris/actions-gh-pages from 3.9.1 to 3.9.2 ([#1841](https://github.com/awslabs/aws-lambda-powertools-python/issues/1841)) -* **deps:** bump future from 0.18.2 to 0.18.3 ([#1836](https://github.com/awslabs/aws-lambda-powertools-python/issues/1836)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.4 to 2.0.5 ([#1837](https://github.com/awslabs/aws-lambda-powertools-python/issues/1837)) -* **deps-dev:** bump mkdocs-material from 9.0.4 to 9.0.5 ([#1840](https://github.com/awslabs/aws-lambda-powertools-python/issues/1840)) -* **deps-dev:** bump types-requests from 2.28.11.7 to 2.28.11.8 ([#1843](https://github.com/awslabs/aws-lambda-powertools-python/issues/1843)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.30 to 1.26.52 ([#1847](https://github.com/awslabs/aws-lambda-powertools-python/issues/1847)) -* **deps-dev:** bump pytest from 7.2.0 to 7.2.1 ([#1838](https://github.com/awslabs/aws-lambda-powertools-python/issues/1838)) -* **deps-dev:** bump aws-cdk-lib from 2.60.0 to 2.61.1 ([#1849](https://github.com/awslabs/aws-lambda-powertools-python/issues/1849)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.49 to 1.26.53 ([#1850](https://github.com/awslabs/aws-lambda-powertools-python/issues/1850)) -* **deps-dev:** bump mkdocs-material from 9.0.5 to 9.0.6 ([#1851](https://github.com/awslabs/aws-lambda-powertools-python/issues/1851)) -* **deps-dev:** bump mkdocs-material from 9.0.3 to 9.0.4 ([#1833](https://github.com/awslabs/aws-lambda-powertools-python/issues/1833)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.43 to 1.26.49 ([#1834](https://github.com/awslabs/aws-lambda-powertools-python/issues/1834)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.40 to 1.26.49 ([#1835](https://github.com/awslabs/aws-lambda-powertools-python/issues/1835)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.18 to 1.26.49 ([#1832](https://github.com/awslabs/aws-lambda-powertools-python/issues/1832)) -* **deps-dev:** bump aws-cdk-lib from 2.59.0 to 2.60.0 ([#1831](https://github.com/awslabs/aws-lambda-powertools-python/issues/1831)) - - - -## [v2.6.0] - 2023-01-12 -## Bug Fixes - -* **api_gateway:** fixed custom metrics issue when using debug mode ([#1827](https://github.com/awslabs/aws-lambda-powertools-python/issues/1827)) - -## Documentation - -* **logger:** fix incorrect field names in example structured logs ([#1830](https://github.com/awslabs/aws-lambda-powertools-python/issues/1830)) -* **logger:** Add warning of uncaught exceptions ([#1826](https://github.com/awslabs/aws-lambda-powertools-python/issues/1826)) - -## Maintenance - * update v2 layer ARN on documentation -* **deps:** bump pydantic from 1.10.2 to 1.10.4 ([#1817](https://github.com/awslabs/aws-lambda-powertools-python/issues/1817)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.1 to 2.0.3 ([#1801](https://github.com/awslabs/aws-lambda-powertools-python/issues/1801)) -* **deps:** bump release-drafter/release-drafter from 5.21.1 to 5.22.0 ([#1802](https://github.com/awslabs/aws-lambda-powertools-python/issues/1802)) -* **deps:** bump gitpython from 3.1.29 to 3.1.30 ([#1812](https://github.com/awslabs/aws-lambda-powertools-python/issues/1812)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.3 to 2.0.4 ([#1821](https://github.com/awslabs/aws-lambda-powertools-python/issues/1821)) -* **deps:** bump peaceiris/actions-gh-pages from 3.9.0 to 3.9.1 ([#1814](https://github.com/awslabs/aws-lambda-powertools-python/issues/1814)) -* **deps-dev:** bump mkdocs-material from 8.5.11 to 9.0.2 ([#1808](https://github.com/awslabs/aws-lambda-powertools-python/issues/1808)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.11.post1 to 1.26.43 ([#1819](https://github.com/awslabs/aws-lambda-powertools-python/issues/1819)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.27 to 1.26.43 ([#1820](https://github.com/awslabs/aws-lambda-powertools-python/issues/1820)) -* **deps-dev:** bump filelock from 3.8.2 to 3.9.0 ([#1816](https://github.com/awslabs/aws-lambda-powertools-python/issues/1816)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.11.post1 to 1.26.35.post1 ([#1818](https://github.com/awslabs/aws-lambda-powertools-python/issues/1818)) -* **deps-dev:** bump ijson from 3.1.4 to 3.2.0.post0 ([#1815](https://github.com/awslabs/aws-lambda-powertools-python/issues/1815)) -* **deps-dev:** bump coverage from 6.5.0 to 7.0.3 ([#1806](https://github.com/awslabs/aws-lambda-powertools-python/issues/1806)) -* **deps-dev:** bump flake8-builtins from 2.0.1 to 2.1.0 ([#1799](https://github.com/awslabs/aws-lambda-powertools-python/issues/1799)) -* **deps-dev:** bump coverage from 7.0.3 to 7.0.4 ([#1822](https://github.com/awslabs/aws-lambda-powertools-python/issues/1822)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.12 to 1.26.40 ([#1811](https://github.com/awslabs/aws-lambda-powertools-python/issues/1811)) -* **deps-dev:** bump isort from 5.11.3 to 5.11.4 ([#1809](https://github.com/awslabs/aws-lambda-powertools-python/issues/1809)) -* **deps-dev:** bump aws-cdk-lib from 2.55.1 to 2.59.0 ([#1810](https://github.com/awslabs/aws-lambda-powertools-python/issues/1810)) -* **deps-dev:** bump importlib-metadata from 5.1.0 to 6.0.0 ([#1804](https://github.com/awslabs/aws-lambda-powertools-python/issues/1804)) -* **deps-dev:** bump mkdocs-material from 9.0.2 to 9.0.3 ([#1823](https://github.com/awslabs/aws-lambda-powertools-python/issues/1823)) -* **deps-dev:** bump black from 22.10.0 to 22.12.0 ([#1770](https://github.com/awslabs/aws-lambda-powertools-python/issues/1770)) -* **deps-dev:** bump flake8-black from 0.3.5 to 0.3.6 ([#1792](https://github.com/awslabs/aws-lambda-powertools-python/issues/1792)) -* **deps-dev:** bump coverage from 7.0.4 to 7.0.5 ([#1829](https://github.com/awslabs/aws-lambda-powertools-python/issues/1829)) -* **deps-dev:** bump types-requests from 2.28.11.5 to 2.28.11.7 ([#1795](https://github.com/awslabs/aws-lambda-powertools-python/issues/1795)) - - - -## [v2.5.0] - 2022-12-21 -## Bug Fixes - -* **event_handlers:** omit explicit None HTTP header values ([#1793](https://github.com/awslabs/aws-lambda-powertools-python/issues/1793)) - -## Documentation - -* **idempotency:** fix, improve, and increase visibility for batch integration ([#1776](https://github.com/awslabs/aws-lambda-powertools-python/issues/1776)) -* **validation:** fix broken link; enrich built-in jmespath links ([#1777](https://github.com/awslabs/aws-lambda-powertools-python/issues/1777)) - -## Features - -* **logger:** unwrap event from common models if asked to log ([#1778](https://github.com/awslabs/aws-lambda-powertools-python/issues/1778)) - -## Maintenance - * update v2 layer ARN on documentation -* **common:** reusable function to extract event from models -* **deps:** bump certifi from 2022.9.24 to 2022.12.7 ([#1768](https://github.com/awslabs/aws-lambda-powertools-python/issues/1768)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.4.0 to 2.0.1 ([#1752](https://github.com/awslabs/aws-lambda-powertools-python/issues/1752)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.3.0 to 1.4.0 ([#1749](https://github.com/awslabs/aws-lambda-powertools-python/issues/1749)) -* **deps-dev:** bump pytest-asyncio from 0.20.2 to 0.20.3 ([#1767](https://github.com/awslabs/aws-lambda-powertools-python/issues/1767)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.0.post1 to 1.26.17 ([#1753](https://github.com/awslabs/aws-lambda-powertools-python/issues/1753)) -* **deps-dev:** bump isort from 5.10.1 to 5.11.2 ([#1782](https://github.com/awslabs/aws-lambda-powertools-python/issues/1782)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.17 to 1.26.30 ([#1785](https://github.com/awslabs/aws-lambda-powertools-python/issues/1785)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.13.post16 to 1.26.24 ([#1765](https://github.com/awslabs/aws-lambda-powertools-python/issues/1765)) -* **deps-dev:** bump aws-cdk-lib from 2.54.0 to 2.55.1 ([#1787](https://github.com/awslabs/aws-lambda-powertools-python/issues/1787)) -* **deps-dev:** bump aws-cdk-lib from 2.53.0 to 2.54.0 ([#1764](https://github.com/awslabs/aws-lambda-powertools-python/issues/1764)) -* **deps-dev:** bump flake8-bugbear from 22.10.27 to 22.12.6 ([#1760](https://github.com/awslabs/aws-lambda-powertools-python/issues/1760)) -* **deps-dev:** bump filelock from 3.8.0 to 3.8.2 ([#1759](https://github.com/awslabs/aws-lambda-powertools-python/issues/1759)) -* **deps-dev:** bump pytest-xdist from 3.0.2 to 3.1.0 ([#1758](https://github.com/awslabs/aws-lambda-powertools-python/issues/1758)) -* **deps-dev:** bump mkdocs-material from 8.5.10 to 8.5.11 ([#1756](https://github.com/awslabs/aws-lambda-powertools-python/issues/1756)) -* **deps-dev:** bump importlib-metadata from 4.13.0 to 5.1.0 ([#1750](https://github.com/awslabs/aws-lambda-powertools-python/issues/1750)) -* **deps-dev:** bump isort from 5.11.2 to 5.11.3 ([#1788](https://github.com/awslabs/aws-lambda-powertools-python/issues/1788)) -* **deps-dev:** bump flake8-black from 0.3.3 to 0.3.5 ([#1738](https://github.com/awslabs/aws-lambda-powertools-python/issues/1738)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.17 to 1.26.27 ([#1775](https://github.com/awslabs/aws-lambda-powertools-python/issues/1775)) -* **tests:** move shared_functions to unit tests - - - -## [v2.4.0] - 2022-11-24 -## Bug Fixes - -* **ci:** use gh-pages env as official docs are wrong -* **ci:** api docs path - -## Documentation - -* **idempotency:** fix register_lambda_context order ([#1747](https://github.com/awslabs/aws-lambda-powertools-python/issues/1747)) -* **streaming:** fix leftover newline - -## Features - -* **streaming:** add new s3 streaming utility ([#1719](https://github.com/awslabs/aws-lambda-powertools-python/issues/1719)) - -## Maintenance - * update v2 layer ARN on documentation -* **ci:** re-create versioned API docs for new pages deployment -* **ci:** re-create versioned API docs for new pages deployment -* **ci:** increase permission in parent job for docs publishing -* **ci:** attempt gh-pages deployment via beta route -* **deps:** bump aws-xray-sdk from 2.10.0 to 2.11.0 ([#1730](https://github.com/awslabs/aws-lambda-powertools-python/issues/1730)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.0.post1 to 1.26.12 ([#1742](https://github.com/awslabs/aws-lambda-powertools-python/issues/1742)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.0.post1 to 1.26.11.post1 ([#1746](https://github.com/awslabs/aws-lambda-powertools-python/issues/1746)) -* **deps-dev:** bump aws-cdk-lib from 2.50.0 to 2.51.1 ([#1741](https://github.com/awslabs/aws-lambda-powertools-python/issues/1741)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.0.post1 to 1.26.13.post16 ([#1743](https://github.com/awslabs/aws-lambda-powertools-python/issues/1743)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.0.post1 to 1.26.12 ([#1744](https://github.com/awslabs/aws-lambda-powertools-python/issues/1744)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.4 to 1.26.11.post1 ([#1740](https://github.com/awslabs/aws-lambda-powertools-python/issues/1740)) -* **deps-dev:** bump types-requests from 2.28.11.4 to 2.28.11.5 ([#1729](https://github.com/awslabs/aws-lambda-powertools-python/issues/1729)) -* **deps-dev:** bump mkdocs-material from 8.5.9 to 8.5.10 ([#1731](https://github.com/awslabs/aws-lambda-powertools-python/issues/1731)) -* **governance:** remove markdown rendering from docs issue template - -## Regression - -* **ci:** new gh-pages beta doesn't work either; reverting as gh-pages is disrupted - - - -## [v2.3.1] - 2022-11-21 -## Bug Fixes - -* **apigateway:** support dynamic routes with equal sign (RFC3986) ([#1737](https://github.com/awslabs/aws-lambda-powertools-python/issues/1737)) - -## Maintenance - * update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* update project description +* merge v2 branch +* bump to 1.24.1 +* bump pyproject version to 2.0 +* bump to 1.24.1 +* bump to 1.24.2 +* use isinstance over type +* correct docs +* correct docs +* bump to 1.25.0 +* bump to 1.25.1 +* bump to 1.25.2 +* bump to 1.25.3 +* lint unused import +* remove unnecessary test +* comment reason for change +* remove duplicate test +* bump to 1.25.4 +* update v2 layer ARN on documentation +* bump to 1.25.5 +* bump to 1.25.6 +* bump to 1.25.7 +* bump to 1.25.8 +* bump to 1.25.9 +* add dummy v2 sar deploy job +* update v2 layer ARN on documentation +* bump layer version to 38 +* bump to 1.25.10 +* update v2 layer ARN on documentation +* include regression in changelog +* bump to 1.26.0 +* bump version 1.26.1 +* print full event depth +* add sam build gitignore +* move to approach B for multiple IaC * test build layer hardware to 8 core -* **deps-dev:** bump mypy-boto3-xray from 1.26.9 to 1.26.11.post1 ([#1734](https://github.com/awslabs/aws-lambda-powertools-python/issues/1734)) - - - -## [v2.3.0] - 2022-11-17 -## Bug Fixes - -* **apigateway:** support nested router decorators ([#1709](https://github.com/awslabs/aws-lambda-powertools-python/issues/1709)) -* **ci:** increase permission to allow version sync back to repo -* **ci:** disable pre-commit hook download from version bump -* **ci:** setup git client earlier to prevent dirty stash error -* **parameters:** get_secret correctly return SecretBinary value ([#1717](https://github.com/awslabs/aws-lambda-powertools-python/issues/1717)) - -## Documentation - -* project name consistency -* **apigateway:** add all resolvers in testing your code section for accuracy ([#1688](https://github.com/awslabs/aws-lambda-powertools-python/issues/1688)) -* **examples:** linting unnecessary whitespace -* **homepage:** update default value for `POWERTOOLS_DEV` ([#1695](https://github.com/awslabs/aws-lambda-powertools-python/issues/1695)) -* **idempotency:** add missing Lambda Context; note on thread-safe ([#1732](https://github.com/awslabs/aws-lambda-powertools-python/issues/1732)) -* **logger:** update uncaught exception message value - -## Features - -* **apigateway:** multiple exceptions in exception_handler ([#1707](https://github.com/awslabs/aws-lambda-powertools-python/issues/1707)) -* **event_sources:** extract CloudWatch Logs in Kinesis streams ([#1710](https://github.com/awslabs/aws-lambda-powertools-python/issues/1710)) -* **logger:** log uncaught exceptions via system's exception hook ([#1727](https://github.com/awslabs/aws-lambda-powertools-python/issues/1727)) -* **parser:** export Pydantic.errors through escape hatch ([#1728](https://github.com/awslabs/aws-lambda-powertools-python/issues/1728)) -* **parser:** extract CloudWatch Logs in Kinesis streams ([#1726](https://github.com/awslabs/aws-lambda-powertools-python/issues/1726)) - -## Maintenance - -* apigw test event wrongly set with base64 * update v2 layer ARN on documentation -* **ci:** revert custom hw for E2E due to lack of hw -* **ci:** try bigger hardware for e2e test -* **ci:** uncomment test pypi, fix version bump sync -* **ci:** limit to src only to prevent dependabot failures -* **ci:** use new custom hw for E2E -* **ci:** prevent dependabot updates to trigger E2E -* **ci:** bump hardware for build steps -* **deps:** bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 ([#1689](https://github.com/awslabs/aws-lambda-powertools-python/issues/1689)) -* **deps-dev:** bump types-requests from 2.28.11.3 to 2.28.11.4 ([#1701](https://github.com/awslabs/aws-lambda-powertools-python/issues/1701)) -* **deps-dev:** bump mypy-boto3-s3 from 1.25.0 to 1.26.0.post1 ([#1716](https://github.com/awslabs/aws-lambda-powertools-python/issues/1716)) -* **deps-dev:** bump mypy-boto3-appconfigdata from 1.25.0 to 1.26.0.post1 ([#1704](https://github.com/awslabs/aws-lambda-powertools-python/issues/1704)) -* **deps-dev:** bump mypy-boto3-xray from 1.25.0 to 1.26.0.post1 ([#1703](https://github.com/awslabs/aws-lambda-powertools-python/issues/1703)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.25.0 to 1.26.0.post1 ([#1714](https://github.com/awslabs/aws-lambda-powertools-python/issues/1714)) -* **deps-dev:** bump flake8-bugbear from 22.10.25 to 22.10.27 ([#1665](https://github.com/awslabs/aws-lambda-powertools-python/issues/1665)) -* **deps-dev:** bump mypy-boto3-lambda from 1.25.0 to 1.26.0.post1 ([#1705](https://github.com/awslabs/aws-lambda-powertools-python/issues/1705)) -* **deps-dev:** bump mypy-boto3-xray from 1.26.0.post1 to 1.26.9 ([#1720](https://github.com/awslabs/aws-lambda-powertools-python/issues/1720)) -* **deps-dev:** bump mypy-boto3-logs from 1.25.0 to 1.26.3 ([#1702](https://github.com/awslabs/aws-lambda-powertools-python/issues/1702)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.0.post1 to 1.26.4 ([#1721](https://github.com/awslabs/aws-lambda-powertools-python/issues/1721)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.25.0 to 1.26.0.post1 ([#1722](https://github.com/awslabs/aws-lambda-powertools-python/issues/1722)) -* **deps-dev:** bump pytest-asyncio from 0.20.1 to 0.20.2 ([#1723](https://github.com/awslabs/aws-lambda-powertools-python/issues/1723)) -* **deps-dev:** bump flake8-builtins from 2.0.0 to 2.0.1 ([#1715](https://github.com/awslabs/aws-lambda-powertools-python/issues/1715)) -* **deps-dev:** bump pytest-xdist from 2.5.0 to 3.0.2 ([#1655](https://github.com/awslabs/aws-lambda-powertools-python/issues/1655)) -* **deps-dev:** bump mkdocs-material from 8.5.7 to 8.5.9 ([#1697](https://github.com/awslabs/aws-lambda-powertools-python/issues/1697)) -* **deps-dev:** bump flake8-comprehensions from 3.10.0 to 3.10.1 ([#1699](https://github.com/awslabs/aws-lambda-powertools-python/issues/1699)) -* **deps-dev:** bump types-requests from 2.28.11.2 to 2.28.11.3 ([#1698](https://github.com/awslabs/aws-lambda-powertools-python/issues/1698)) -* **deps-dev:** bump pytest-benchmark from 3.4.1 to 4.0.0 ([#1659](https://github.com/awslabs/aws-lambda-powertools-python/issues/1659)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.25.0 to 1.26.0.post1 ([#1691](https://github.com/awslabs/aws-lambda-powertools-python/issues/1691)) -* **deps-dev:** bump mypy-boto3-ssm from 1.25.0 to 1.26.0.post1 ([#1690](https://github.com/awslabs/aws-lambda-powertools-python/issues/1690)) -* **logger:** uncaught exception to use exception value as message -* **logger:** overload inject_lambda_context with generics ([#1583](https://github.com/awslabs/aws-lambda-powertools-python/issues/1583)) - - - -## [v2.2.0] - 2022-11-07 -## Documentation - -* **homepage:** remove v1 layer limitation on pydantic not being included -* **tracer:** add note on why X-Ray SDK over ADOT closes [#1675](https://github.com/awslabs/aws-lambda-powertools-python/issues/1675) - -## Features - -* **metrics:** add EphemeralMetrics as a non-singleton option ([#1676](https://github.com/awslabs/aws-lambda-powertools-python/issues/1676)) -* **parameters:** add get_parameters_by_name for SSM params in distinct paths ([#1678](https://github.com/awslabs/aws-lambda-powertools-python/issues/1678)) - -## Maintenance - -* update v2 layer ARN on documentation -* **deps:** bump package to 2.2.0 -* **deps-dev:** bump aws-cdk-lib from 2.49.0 to 2.50.0 ([#1683](https://github.com/awslabs/aws-lambda-powertools-python/issues/1683)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.25.0 to 1.26.0.post1 ([#1682](https://github.com/awslabs/aws-lambda-powertools-python/issues/1682)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.25.0 to 1.26.0.post1 ([#1679](https://github.com/awslabs/aws-lambda-powertools-python/issues/1679)) -* **package:** correct pyproject version manually - - - -## [v2.1.0] - 2022-10-31 -## Bug Fixes - -* **ci:** linting issues after flake8-blackbear,mypy upgrades -* **deps:** update build system to poetry-core ([#1651](https://github.com/awslabs/aws-lambda-powertools-python/issues/1651)) -* **idempotency:** idempotent_function should support standalone falsy values ([#1669](https://github.com/awslabs/aws-lambda-powertools-python/issues/1669)) -* **logger:** fix unknown attributes being ignored by mypy ([#1670](https://github.com/awslabs/aws-lambda-powertools-python/issues/1670)) - -## Documentation - -* **community:** fix social handlers for Ran ([#1654](https://github.com/awslabs/aws-lambda-powertools-python/issues/1654)) -* **community:** fix twitch parent domain for embedded video -* **homepage:** remove 3.6 and add hero image -* **homepage:** add Pulumi code example ([#1652](https://github.com/awslabs/aws-lambda-powertools-python/issues/1652)) -* **index:** fold support us banner -* **index:** add quotes to pip for zsh customers -* **install:** address early v2 feedback on installation and project support -* **we-made-this:** new community content section ([#1650](https://github.com/awslabs/aws-lambda-powertools-python/issues/1650)) - -## Features - -* **layers:** add layer balancer script ([#1643](https://github.com/awslabs/aws-lambda-powertools-python/issues/1643)) -* **logger:** add use_rfc3339 and auto-complete formatter opts in Logger ([#1662](https://github.com/awslabs/aws-lambda-powertools-python/issues/1662)) -* **logger:** accept arbitrary keyword=value for ephemeral metadata ([#1658](https://github.com/awslabs/aws-lambda-powertools-python/issues/1658)) - -## Maintenance - -* update v2 layer ARN on documentation -* **ci:** fix typo on version description -* **deps:** bump peaceiris/actions-gh-pages from 3.8.0 to 3.9.0 ([#1649](https://github.com/awslabs/aws-lambda-powertools-python/issues/1649)) -* **deps:** bump docker/setup-qemu-action from 2.0.0 to 2.1.0 ([#1627](https://github.com/awslabs/aws-lambda-powertools-python/issues/1627)) -* **deps-dev:** bump aws-cdk-lib from 2.47.0 to 2.48.0 ([#1664](https://github.com/awslabs/aws-lambda-powertools-python/issues/1664)) -* **deps-dev:** bump flake8-variables-names from 0.0.4 to 0.0.5 ([#1628](https://github.com/awslabs/aws-lambda-powertools-python/issues/1628)) -* **deps-dev:** bump pytest-asyncio from 0.16.0 to 0.20.1 ([#1635](https://github.com/awslabs/aws-lambda-powertools-python/issues/1635)) -* **deps-dev:** bump aws-cdk-lib from 2.48.0 to 2.49.0 ([#1671](https://github.com/awslabs/aws-lambda-powertools-python/issues/1671)) -* **docs:** remove v2 banner on top of the docs -* **governance:** remove 'area/' from PR labels - - - -## [v2.0.0] - 2022-10-24 -## Bug Fixes - -* lock dependencies -* mypy errors -* lint files -* **ci:** temporarly remove pypi test deployment -* **ci:** use docker driver on buildx -* **ci:** new artifact path, sed gnu/linux syntax, and pypi test -* **ci:** secret and OIDC inheritance in nested children workflow -* **ci:** build without buildkit -* **ci:** fix arm64 layer builds -* **ci:** remove v2 suffix from SAR apps ([#1633](https://github.com/awslabs/aws-lambda-powertools-python/issues/1633)) -* **ci:** workflow should use npx for CDK CLI -* **parser:** S3Model Object Deleted omits size and eTag attr ([#1638](https://github.com/awslabs/aws-lambda-powertools-python/issues/1638)) - -## Code Refactoring - -* **apigateway:** remove POWERTOOLS_EVENT_HANDLER_DEBUG env var ([#1620](https://github.com/awslabs/aws-lambda-powertools-python/issues/1620)) -* **batch:** remove legacy sqs_batch_processor ([#1492](https://github.com/awslabs/aws-lambda-powertools-python/issues/1492)) -* **e2e:** make table name dynamic -* **e2e:** fix idempotency typing - -## Documentation - -* **batch:** remove legacy reference to sqs processor -* **homepage:** note about v2 version -* **homepage:** auto-update Layer ARN on every release ([#1610](https://github.com/awslabs/aws-lambda-powertools-python/issues/1610)) -* **roadmap:** refresh roadmap post-v2 launch -* **roadmap:** include observability provider and lambda layer themes before v2 -* **upgrade_guide:** add latest changes and quick summary ([#1623](https://github.com/awslabs/aws-lambda-powertools-python/issues/1623)) -* **v2:** document optional dependencies and local dev ([#1574](https://github.com/awslabs/aws-lambda-powertools-python/issues/1574)) - -## Features - -* **apigateway:** ignore trailing slashes in routes (APIGatewayRestResolver) ([#1609](https://github.com/awslabs/aws-lambda-powertools-python/issues/1609)) -* **ci:** release docs as alpha when doing a pre-release ([#1624](https://github.com/awslabs/aws-lambda-powertools-python/issues/1624)) -* **data-classes:** replace AttributeValue in DynamoDBStreamEvent with deserialized Python values ([#1619](https://github.com/awslabs/aws-lambda-powertools-python/issues/1619)) -* **data_classes:** add KinesisFirehoseEvent ([#1540](https://github.com/awslabs/aws-lambda-powertools-python/issues/1540)) -* **event_handler:** improved support for headers and cookies in v2 ([#1455](https://github.com/awslabs/aws-lambda-powertools-python/issues/1455)) -* **event_handler:** add cookies as 1st class citizen in v2 ([#1487](https://github.com/awslabs/aws-lambda-powertools-python/issues/1487)) -* **idempotency:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1535](https://github.com/awslabs/aws-lambda-powertools-python/issues/1535)) -* **layer:** publish SAR v2 via Github actions ([#1585](https://github.com/awslabs/aws-lambda-powertools-python/issues/1585)) -* **layers:** add support for publishing v2 layer ([#1558](https://github.com/awslabs/aws-lambda-powertools-python/issues/1558)) -* **parameters:** migrate AppConfig to new APIs due to API deprecation ([#1553](https://github.com/awslabs/aws-lambda-powertools-python/issues/1553)) -* **tracer:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1486](https://github.com/awslabs/aws-lambda-powertools-python/issues/1486)) - -## Maintenance - * update v2 layer ARN on documentation +* bump to version 1.26.3 +* fix var expr +* dummy for PR test * update v2 layer ARN on documentation +* remove leftover from fork one more time * update v2 layer ARN on documentation * update v2 layer ARN on documentation -* merge v2 branch -* bump pyproject version to 2.0 -* **ci:** make release process manual +* debug full event +* print full workflow event depth +* bump to 1.26.2 +* **batch:** deprecate sqs_batch_processor ([#1463](https://github.com/awslabs/aws-lambda-powertools-python/issues/1463)) +* **ci:** use new custom hw for E2E +* **ci:** re-create versioned API docs for new pages deployment +* **ci:** add end to end testing mechanism ([#1247](https://github.com/awslabs/aws-lambda-powertools-python/issues/1247)) +* **ci:** improve error handling for non-issue numbers +* **ci:** experiment with conditional on outputs +* **ci:** automatically add area label based on title ([#1300](https://github.com/awslabs/aws-lambda-powertools-python/issues/1300)) +* **ci:** lockdown 3rd party workflows to pin sha ([#1301](https://github.com/awslabs/aws-lambda-powertools-python/issues/1301)) +* **ci:** disable output debugging as pr body isnt accepted +* **ci:** increase release automation and limit to one manual step ([#1297](https://github.com/awslabs/aws-lambda-powertools-python/issues/1297)) +* **ci:** update project with version 1.26.4 +* **ci:** adds caching when installing python dependencies ([#1311](https://github.com/awslabs/aws-lambda-powertools-python/issues/1311)) +* **ci:** update project with version 1.26.5 +* **ci:** confirm workflow_run event +* **ci:** auto-merge cdk lib and lambda layer construct +* **ci:** move all scripts under .github/scripts +* **ci:** move error prone env to code as constants +* **ci:** make export PR reusable +* **ci:** experiment hardening origin +* **ci:** experiment hardening origin +* **ci:** test default env +* **ci:** test env expr +* **ci:** test upstream job skip +* **ci:** lockdown workflow_run by origin ([#1350](https://github.com/awslabs/aws-lambda-powertools-python/issues/1350)) +* **ci:** convert inline gh-script to file +* **ci:** fix reference error in related_issue +* **ci:** introduce codeowners ([#1352](https://github.com/awslabs/aws-lambda-powertools-python/issues/1352)) +* **ci:** use OIDC and encrypt release secrets ([#1355](https://github.com/awslabs/aws-lambda-powertools-python/issues/1355)) +* **ci:** remove core group from codeowners ([#1358](https://github.com/awslabs/aws-lambda-powertools-python/issues/1358)) +* **ci:** use gh environment for beta and prod layer deploy ([#1356](https://github.com/awslabs/aws-lambda-powertools-python/issues/1356)) +* **ci:** update project with version 1.26.6 +* **ci:** add conditional to skip pypi release ([#1366](https://github.com/awslabs/aws-lambda-powertools-python/issues/1366)) +* **ci:** update project with version 1.26.6 +* **ci:** update project with version 1.26.6 +* **ci:** increase skip_pypi logic to cover tests/changelog on re-run failures +* **ci:** remove leftover logic from on_merged_pr workflow +* **ci:** drop 3.6 from workflows +* **ci:** drop 3.6 from workflows ([#1395](https://github.com/awslabs/aws-lambda-powertools-python/issues/1395)) +* **ci:** temporarily disable changelog push on release +* **ci:** move changelog generation to rebuild_latest_doc workflow +* **ci:** update project with version +* **ci:** move changelog generation to rebuild_latest_doc workflow +* **ci:** readd changelog step on release +* **ci:** update release automated activities +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** add manual trigger for docs +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** limits concurrency for docs workflow +* **ci:** sync area labels to prevent dedup +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** reduce payload and only send prod notification +* **ci:** remove area/utilities conflicting label +* **ci:** remove conventional changelog commit to reduce noise +* **ci:** fix reference error in related_issue +* **ci:** temp disable e2e matrix +* **ci:** include py version in stack and cache lock +* **ci:** revert e2e py version matrix +* **ci:** disable e2e py version matrix due to concurrent locking +* **ci:** prevent concurrent git update in critical workflows ([#1478](https://github.com/awslabs/aws-lambda-powertools-python/issues/1478)) +* **ci:** limit E2E workflow run for source code change +* **ci:** limits concurrency for docs workflow +* **ci:** add missing description fields +* **ci:** fix invalid dependency leftover +* **ci:** remove dangling debug step +* **ci:** add linter for GitHub Actions as pre-commit hook ([#1479](https://github.com/awslabs/aws-lambda-powertools-python/issues/1479)) +* **ci:** add workflow to suggest splitting large PRs ([#1480](https://github.com/awslabs/aws-lambda-powertools-python/issues/1480)) +* **ci:** enable ci checks for v2 +* **ci:** re-create versioned API docs for new pages deployment +* **ci:** increase permission in parent job for docs publishing +* **ci:** attempt gh-pages deployment via beta route +* **ci:** reactivate on_merged_pr workflow +* **ci:** record pr details upon labeling +* **ci:** destructure assignment on comment_large_pr +* **ci:** add note for state persistence on comment_large_pr +* **ci:** format comment on comment_large_pr script +* **ci:** create reusable docs publishing workflow ([#1482](https://github.com/awslabs/aws-lambda-powertools-python/issues/1482)) +* **ci:** create docs workflow for v2 +* **ci:** create adhoc docs workflow for v2 +* **ci:** create adhoc docs workflow for v2 +* **ci:** sync package version with pypi +* **ci:** disable v2 docs +* **ci:** improve wording on closed issues action +* **ci:** deactivate on_merged_pr workflow +* **ci:** changelog pre-generation to fetch tags from origin +* **ci:** post release on tagged issues too +* **ci:** disable mergify configuration after breaking changes ([#1188](https://github.com/awslabs/aws-lambda-powertools-python/issues/1188)) +* **ci:** bump hardware for build steps +* **ci:** try bigger hardware for e2e test +* **ci:** uncomment test pypi, fix version bump sync * **ci:** migrate E2E tests to CDK CLI and off Docker ([#1501](https://github.com/awslabs/aws-lambda-powertools-python/issues/1501)) +* **ci:** replace deprecated set-output commands ([#1957](https://github.com/awslabs/aws-lambda-powertools-python/issues/1957)) * **ci:** remove v1 workflows ([#1617](https://github.com/awslabs/aws-lambda-powertools-python/issues/1617)) +* **ci:** run codeql analysis on push only +* **ci:** disable pypi test due to maintenance mode +* **ci:** fix mergify dependabot queue +* **ci:** make release process manual +* **ci:** add queue name in mergify +* **ci:** remove mergify legacy key +* **ci:** fix typo on version description +* **ci:** update mergify bot breaking change +* **ci:** safely label PR based on title +* **ci:** split latest docs workflow +* **ci:** limit to src only to prevent dependabot failures +* **ci:** revert custom hw for E2E due to lack of hw +* **ci:** prevent dependabot updates to trigger E2E +* **ci:** remove unused and undeclared OS matrix env +* **common:** reusable function to extract event from models * **core:** expose modules in the Top-level package ([#1517](https://github.com/awslabs/aws-lambda-powertools-python/issues/1517)) * **dep:** add cfn-lint as a dev dependency; pre-commit ([#1612](https://github.com/awslabs/aws-lambda-powertools-python/issues/1612)) -* **deps:** remove email-validator; use Str over EmailStr in SES model ([#1608](https://github.com/awslabs/aws-lambda-powertools-python/issues/1608)) -* **deps:** bump release-drafter/release-drafter from 5.21.0 to 5.21.1 ([#1611](https://github.com/awslabs/aws-lambda-powertools-python/issues/1611)) -* **deps:** lock importlib to 4.x -* **deps-dev:** bump mypy-boto3-s3 from 1.24.76 to 1.24.94 ([#1622](https://github.com/awslabs/aws-lambda-powertools-python/issues/1622)) -* **deps-dev:** bump aws-cdk-lib from 2.46.0 to 2.47.0 ([#1629](https://github.com/awslabs/aws-lambda-powertools-python/issues/1629)) -* **layer:** bump to 1.31.1 (v39) - - - -## [v1.31.1] - 2022-10-14 -## Bug Fixes - -* **parser:** loose validation on SNS fields to support FIFO ([#1606](https://github.com/awslabs/aws-lambda-powertools-python/issues/1606)) - -## Documentation - -* **governance:** allow community to suggest feature content ([#1593](https://github.com/awslabs/aws-lambda-powertools-python/issues/1593)) -* **governance:** new form to allow customers self-nominate as public reference ([#1589](https://github.com/awslabs/aws-lambda-powertools-python/issues/1589)) -* **homepage:** include .NET powertools -* **idempotency:** "persisntence" typo ([#1596](https://github.com/awslabs/aws-lambda-powertools-python/issues/1596)) -* **logger:** fix typo. ([#1587](https://github.com/awslabs/aws-lambda-powertools-python/issues/1587)) - -## Maintenance - -* add dummy v2 sar deploy job -* bump layer version to 38 -* **deps-dev:** bump mypy-boto3-ssm from 1.24.81 to 1.24.90 ([#1594](https://github.com/awslabs/aws-lambda-powertools-python/issues/1594)) -* **deps-dev:** bump flake8-builtins from 1.5.3 to 2.0.0 ([#1582](https://github.com/awslabs/aws-lambda-powertools-python/issues/1582)) - - - -## [v1.31.0] - 2022-10-10 -## Bug Fixes - -* **metrics:** ensure dimension_set is reused across instances (pointer) ([#1581](https://github.com/awslabs/aws-lambda-powertools-python/issues/1581)) - -## Documentation - -* **readme:** add lambda layer latest version badge - -## Features - -* **parser:** add KinesisFirehoseModel ([#1556](https://github.com/awslabs/aws-lambda-powertools-python/issues/1556)) - -## Maintenance - -* **deps-dev:** bump types-requests from 2.28.11.1 to 2.28.11.2 ([#1576](https://github.com/awslabs/aws-lambda-powertools-python/issues/1576)) -* **deps-dev:** bump typing-extensions from 4.3.0 to 4.4.0 ([#1575](https://github.com/awslabs/aws-lambda-powertools-python/issues/1575)) -* **layer:** remove unsused GetFunction permission for the canary -* **layer:** bump to latest version 37 - - - -## [v1.30.0] - 2022-10-05 -## Bug Fixes - -* **apigateway:** update Response class to require status_code only ([#1560](https://github.com/awslabs/aws-lambda-powertools-python/issues/1560)) -* **ci:** integrate isort 5.0 with black to resolve conflicts -* **event_sources:** implement Mapping protocol on DictWrapper for better interop with existing middlewares ([#1516](https://github.com/awslabs/aws-lambda-powertools-python/issues/1516)) -* **typing:** fix mypy error -* **typing:** level arg in copy_config_to_registered_loggers ([#1534](https://github.com/awslabs/aws-lambda-powertools-python/issues/1534)) - -## Documentation - -* **batch:** document the new lambda context feature -* **homepage:** introduce POWERTOOLS_DEV env var ([#1569](https://github.com/awslabs/aws-lambda-powertools-python/issues/1569)) -* **multiple:** fix highlighting after new isort/black integration -* **parser:** add JSON string field extension example ([#1526](https://github.com/awslabs/aws-lambda-powertools-python/issues/1526)) - -## Features - -* **batch:** inject lambda_context if record handler signature accepts it ([#1561](https://github.com/awslabs/aws-lambda-powertools-python/issues/1561)) -* **event-handler:** context support to share data between routers ([#1567](https://github.com/awslabs/aws-lambda-powertools-python/issues/1567)) -* **logger:** introduce POWERTOOLS_DEBUG for internal debugging ([#1572](https://github.com/awslabs/aws-lambda-powertools-python/issues/1572)) -* **logger:** include logger name attribute when copy_config_to_registered_logger is used ([#1568](https://github.com/awslabs/aws-lambda-powertools-python/issues/1568)) -* **logger:** pretty-print JSON when POWERTOOLS_DEV is set ([#1548](https://github.com/awslabs/aws-lambda-powertools-python/issues/1548)) - -## Maintenance - * **dep:** bump pyproject to pypi sync -* **deps:** bump fastjsonschema from 2.16.1 to 2.16.2 ([#1530](https://github.com/awslabs/aws-lambda-powertools-python/issues/1530)) -* **deps:** bump actions/setup-python from 3 to 4 ([#1528](https://github.com/awslabs/aws-lambda-powertools-python/issues/1528)) -* **deps:** bump codecov/codecov-action from 3.1.0 to 3.1.1 ([#1529](https://github.com/awslabs/aws-lambda-powertools-python/issues/1529)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.3.0 to 1.4.0 ([#1749](https://github.com/awslabs/aws-lambda-powertools-python/issues/1749)) +* **deps:** bump pydantic from 1.10.4 to 1.10.5 ([#1931](https://github.com/awslabs/aws-lambda-powertools-python/issues/1931)) +* **deps:** bump github/codeql-action from 1 to 2 ([#1154](https://github.com/awslabs/aws-lambda-powertools-python/issues/1154)) +* **deps:** bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 ([#1689](https://github.com/awslabs/aws-lambda-powertools-python/issues/1689)) +* **deps:** bump email-validator from 1.1.3 to 1.2.1 ([#1199](https://github.com/awslabs/aws-lambda-powertools-python/issues/1199)) +* **deps:** bump pydantic from 1.9.0 to 1.9.1 ([#1221](https://github.com/awslabs/aws-lambda-powertools-python/issues/1221)) +* **deps:** bump package to 2.2.0 +* **deps:** bump actions/setup-python from 2.3.1 to 3 ([#1048](https://github.com/awslabs/aws-lambda-powertools-python/issues/1048)) +* **deps:** bump actions/checkout from 2 to 3 ([#1052](https://github.com/awslabs/aws-lambda-powertools-python/issues/1052)) +* **deps:** bump actions/github-script from 5 to 6 ([#1023](https://github.com/awslabs/aws-lambda-powertools-python/issues/1023)) +* **deps:** bump fastjsonschema from 2.15.2 to 2.15.3 ([#949](https://github.com/awslabs/aws-lambda-powertools-python/issues/949)) +* **deps:** bump actions/setup-python from 3 to 4 ([#1244](https://github.com/awslabs/aws-lambda-powertools-python/issues/1244)) +* **deps:** bump boto3 from 1.18.56 to 1.18.58 ([#755](https://github.com/awslabs/aws-lambda-powertools-python/issues/755)) +* **deps:** bump fastjsonschema from 2.16.2 to 2.16.3 ([#1961](https://github.com/awslabs/aws-lambda-powertools-python/issues/1961)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.0 to 2.1.1 ([#1958](https://github.com/awslabs/aws-lambda-powertools-python/issues/1958)) +* **deps:** bump docker/setup-buildx-action from 2.4.0 to 2.4.1 ([#1903](https://github.com/awslabs/aws-lambda-powertools-python/issues/1903)) +* **deps:** bump dependabot/fetch-metadata from 1.1.1 to 1.3.2 ([#1269](https://github.com/awslabs/aws-lambda-powertools-python/issues/1269)) +* **deps:** bump docker/setup-qemu-action from 2.0.0 to 2.1.0 ([#1627](https://github.com/awslabs/aws-lambda-powertools-python/issues/1627)) +* **deps:** bump aws-xray-sdk from 2.9.0 to 2.10.0 ([#1270](https://github.com/awslabs/aws-lambda-powertools-python/issues/1270)) +* **deps:** bump peaceiris/actions-gh-pages from 3.8.0 to 3.9.0 ([#1649](https://github.com/awslabs/aws-lambda-powertools-python/issues/1649)) +* **deps:** bump dependabot/fetch-metadata from 1.3.2 to 1.3.3 ([#1273](https://github.com/awslabs/aws-lambda-powertools-python/issues/1273)) +* **deps:** bump boto3 from 1.18.58 to 1.18.59 ([#760](https://github.com/awslabs/aws-lambda-powertools-python/issues/760)) +* **deps:** bump pydantic from 1.8.2 to 1.9.0 ([#933](https://github.com/awslabs/aws-lambda-powertools-python/issues/933)) +* **deps:** bump docker/setup-buildx-action from 2.0.0 to 2.4.0 ([#1873](https://github.com/awslabs/aws-lambda-powertools-python/issues/1873)) +* **deps:** bump actions/setup-node from 2 to 3 ([#1281](https://github.com/awslabs/aws-lambda-powertools-python/issues/1281)) +* **deps:** bump aws-cdk-lib from 2.29.0 to 2.31.1 ([#1290](https://github.com/awslabs/aws-lambda-powertools-python/issues/1290)) +* **deps:** bump cdk-lambda-powertools-python-layer ([#1284](https://github.com/awslabs/aws-lambda-powertools-python/issues/1284)) +* **deps:** bump attrs from 21.2.0 to 21.4.0 ([#1282](https://github.com/awslabs/aws-lambda-powertools-python/issues/1282)) +* **deps:** bump jsii from 1.61.0 to 1.62.0 ([#1294](https://github.com/awslabs/aws-lambda-powertools-python/issues/1294)) +* **deps:** bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 ([#1855](https://github.com/awslabs/aws-lambda-powertools-python/issues/1855)) +* **deps:** bump constructs from 10.1.1 to 10.1.46 ([#1306](https://github.com/awslabs/aws-lambda-powertools-python/issues/1306)) +* **deps:** bump codecov/codecov-action from 3.0.0 to 3.1.0 ([#1143](https://github.com/awslabs/aws-lambda-powertools-python/issues/1143)) +* **deps:** remove email-validator; use Str over EmailStr in SES model ([#1608](https://github.com/awslabs/aws-lambda-powertools-python/issues/1608)) +* **deps:** bump release-drafter/release-drafter from 5.21.0 to 5.21.1 ([#1611](https://github.com/awslabs/aws-lambda-powertools-python/issues/1611)) +* **deps:** lock importlib to 4.x +* **deps:** bump fastjsonschema from 2.15.3 to 2.16.1 ([#1309](https://github.com/awslabs/aws-lambda-powertools-python/issues/1309)) +* **deps:** bump constructs from 10.1.1 to 10.1.49 ([#1308](https://github.com/awslabs/aws-lambda-powertools-python/issues/1308)) +* **deps:** bump codecov/codecov-action from 2.1.0 to 3.0.0 ([#1102](https://github.com/awslabs/aws-lambda-powertools-python/issues/1102)) +* **deps:** bump constructs from 10.1.1 to 10.1.51 ([#1323](https://github.com/awslabs/aws-lambda-powertools-python/issues/1323)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.5 to 2.1.0 ([#1943](https://github.com/awslabs/aws-lambda-powertools-python/issues/1943)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.1 to 2.1.2 ([#1979](https://github.com/awslabs/aws-lambda-powertools-python/issues/1979)) +* **deps:** bump fastjsonschema from 2.15.1 to 2.15.2 ([#891](https://github.com/awslabs/aws-lambda-powertools-python/issues/891)) +* **deps:** bump constructs from 10.1.1 to 10.1.52 ([#1343](https://github.com/awslabs/aws-lambda-powertools-python/issues/1343)) +* **deps:** bump peaceiris/actions-gh-pages from 3.9.1 to 3.9.2 ([#1841](https://github.com/awslabs/aws-lambda-powertools-python/issues/1841)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.4 to 2.0.5 ([#1837](https://github.com/awslabs/aws-lambda-powertools-python/issues/1837)) +* **deps:** bump future from 0.18.2 to 0.18.3 ([#1836](https://github.com/awslabs/aws-lambda-powertools-python/issues/1836)) +* **deps:** bump pydantic from 1.10.5 to 1.10.6 ([#1991](https://github.com/awslabs/aws-lambda-powertools-python/issues/1991)) +* **deps:** bump actions/upload-artifact from 2 to 3 ([#1103](https://github.com/awslabs/aws-lambda-powertools-python/issues/1103)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.3 to 2.0.4 ([#1821](https://github.com/awslabs/aws-lambda-powertools-python/issues/1821)) +* **deps:** bump boto3 from 1.18.59 to 1.18.61 ([#766](https://github.com/awslabs/aws-lambda-powertools-python/issues/766)) +* **deps:** bump peaceiris/actions-gh-pages from 3.9.0 to 3.9.1 ([#1814](https://github.com/awslabs/aws-lambda-powertools-python/issues/1814)) * **deps:** bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 ([#1565](https://github.com/awslabs/aws-lambda-powertools-python/issues/1565)) +* **deps:** bump pydantic from 1.10.2 to 1.10.4 ([#1817](https://github.com/awslabs/aws-lambda-powertools-python/issues/1817)) +* **deps:** bump aws-xray-sdk from 2.8.0 to 2.9.0 ([#876](https://github.com/awslabs/aws-lambda-powertools-python/issues/876)) +* **deps:** bump constructs from 10.1.1 to 10.1.59 ([#1396](https://github.com/awslabs/aws-lambda-powertools-python/issues/1396)) +* **deps:** bump jsii from 1.57.0 to 1.63.1 ([#1390](https://github.com/awslabs/aws-lambda-powertools-python/issues/1390)) +* **deps:** support arm64 when developing locally ([#862](https://github.com/awslabs/aws-lambda-powertools-python/issues/862)) +* **deps:** bump gitpython from 3.1.29 to 3.1.30 ([#1812](https://github.com/awslabs/aws-lambda-powertools-python/issues/1812)) +* **deps:** bump jsii from 1.57.0 to 1.63.2 ([#1400](https://github.com/awslabs/aws-lambda-powertools-python/issues/1400)) +* **deps:** bump constructs from 10.1.1 to 10.1.60 ([#1399](https://github.com/awslabs/aws-lambda-powertools-python/issues/1399)) +* **deps:** bump constructs from 10.1.1 to 10.1.63 ([#1402](https://github.com/awslabs/aws-lambda-powertools-python/issues/1402)) +* **deps:** bump attrs from 21.4.0 to 22.1.0 ([#1397](https://github.com/awslabs/aws-lambda-powertools-python/issues/1397)) * **deps:** bump email-validator from 1.2.1 to 1.3.0 ([#1533](https://github.com/awslabs/aws-lambda-powertools-python/issues/1533)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.24.54 to 1.24.83 ([#1557](https://github.com/awslabs/aws-lambda-powertools-python/issues/1557)) -* **deps-dev:** bump mkdocs-material from 8.5.3 to 8.5.4 ([#1563](https://github.com/awslabs/aws-lambda-powertools-python/issues/1563)) -* **deps-dev:** bump pytest-cov from 3.0.0 to 4.0.0 ([#1551](https://github.com/awslabs/aws-lambda-powertools-python/issues/1551)) -* **deps-dev:** bump flake8-bugbear from 22.9.11 to 22.9.23 ([#1541](https://github.com/awslabs/aws-lambda-powertools-python/issues/1541)) -* **deps-dev:** bump types-requests from 2.28.11 to 2.28.11.1 ([#1571](https://github.com/awslabs/aws-lambda-powertools-python/issues/1571)) -* **deps-dev:** bump mypy-boto3-ssm from 1.24.69 to 1.24.80 ([#1542](https://github.com/awslabs/aws-lambda-powertools-python/issues/1542)) -* **deps-dev:** bump mako from 1.2.2 to 1.2.3 ([#1537](https://github.com/awslabs/aws-lambda-powertools-python/issues/1537)) -* **deps-dev:** bump types-requests from 2.28.10 to 2.28.11 ([#1538](https://github.com/awslabs/aws-lambda-powertools-python/issues/1538)) -* **deps-dev:** bump mkdocs-material from 8.5.1 to 8.5.3 ([#1532](https://github.com/awslabs/aws-lambda-powertools-python/issues/1532)) -* **deps-dev:** bump mypy-boto3-ssm from 1.24.80 to 1.24.81 ([#1544](https://github.com/awslabs/aws-lambda-powertools-python/issues/1544)) -* **deps-dev:** bump mypy-boto3-s3 from 1.24.36.post1 to 1.24.76 ([#1531](https://github.com/awslabs/aws-lambda-powertools-python/issues/1531)) -* **docs:** bump layer version to 36 (1.29.2) -* **layers:** add dummy v2 layer automation -* **lint:** use new isort black integration -* **multiple:** localize powertools_dev env logic and warning ([#1570](https://github.com/awslabs/aws-lambda-powertools-python/issues/1570)) - - - -## [v1.29.2] - 2022-09-19 -## Bug Fixes - -* **deps:** bump dev dep mako version to address CVE-2022-40023 ([#1524](https://github.com/awslabs/aws-lambda-powertools-python/issues/1524)) - -## Maintenance - +* **deps:** bump constructs from 10.1.1 to 10.1.64 ([#1405](https://github.com/awslabs/aws-lambda-powertools-python/issues/1405)) +* **deps:** bump fastjsonschema from 2.16.1 to 2.16.2 ([#1530](https://github.com/awslabs/aws-lambda-powertools-python/issues/1530)) +* **deps:** bump codecov/codecov-action from 3.1.0 to 3.1.1 ([#1529](https://github.com/awslabs/aws-lambda-powertools-python/issues/1529)) +* **deps:** bump actions/setup-python from 3 to 4 ([#1528](https://github.com/awslabs/aws-lambda-powertools-python/issues/1528)) +* **deps:** bump constructs from 10.1.1 to 10.1.65 ([#1407](https://github.com/awslabs/aws-lambda-powertools-python/issues/1407)) * **deps:** bump release-drafter/release-drafter from 5.20.1 to 5.21.0 ([#1520](https://github.com/awslabs/aws-lambda-powertools-python/issues/1520)) -* **deps-dev:** bump mkdocs-material from 8.5.0 to 8.5.1 ([#1521](https://github.com/awslabs/aws-lambda-powertools-python/issues/1521)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.60 to 1.24.74 ([#1522](https://github.com/awslabs/aws-lambda-powertools-python/issues/1522)) - - - -## [v1.29.1] - 2022-09-13 - - -## [v1.29.0] - 2022-09-13 -## Bug Fixes - -* **ci:** ignore v2 action for now -* **ci:** only run e2e tests on py 3.7 -* **ci:** pass core fns to large pr workflow script -* **ci:** on_label permissioning model & workflow execution -* **ci:** ensure PR_AUTHOR is present for large_pr_split workflow -* **ci:** gracefully and successful exit changelog upon no changes -* **ci:** event resolution for on_label_added workflow -* **core:** fixes leftovers from rebase - -## Documentation - -* **layer:** upgrade to 1.28.0 (v33) - -## Features - -* **ci:** add actionlint in pre-commit hook -* **data-classes:** add KafkaEvent and KafkaEventRecord ([#1485](https://github.com/awslabs/aws-lambda-powertools-python/issues/1485)) -* **event_sources:** add CloudWatch dashboard custom widget event ([#1474](https://github.com/awslabs/aws-lambda-powertools-python/issues/1474)) -* **parser:** add KafkaMskEventModel and KafkaSelfManagedEventModel ([#1499](https://github.com/awslabs/aws-lambda-powertools-python/issues/1499)) - -## Maintenance - -* **ci:** add workflow to suggest splitting large PRs ([#1480](https://github.com/awslabs/aws-lambda-powertools-python/issues/1480)) -* **ci:** remove unused and undeclared OS matrix env -* **ci:** disable v2 docs -* **ci:** limit E2E workflow run for source code change -* **ci:** add missing description fields -* **ci:** sync package version with pypi -* **ci:** fix invalid dependency leftover -* **ci:** create adhoc docs workflow for v2 -* **ci:** create adhoc docs workflow for v2 -* **ci:** remove dangling debug step -* **ci:** create docs workflow for v2 -* **ci:** create reusable docs publishing workflow ([#1482](https://github.com/awslabs/aws-lambda-powertools-python/issues/1482)) -* **ci:** format comment on comment_large_pr script -* **ci:** add note for state persistence on comment_large_pr -* **ci:** destructure assignment on comment_large_pr -* **ci:** record pr details upon labeling -* **ci:** add linter for GitHub Actions as pre-commit hook ([#1479](https://github.com/awslabs/aws-lambda-powertools-python/issues/1479)) -* **ci:** enable ci checks for v2 -* **deps-dev:** bump black from 21.12b0 to 22.8.0 ([#1515](https://github.com/awslabs/aws-lambda-powertools-python/issues/1515)) +* **deps:** bump actions/setup-python from 2.3.0 to 2.3.1 ([#852](https://github.com/awslabs/aws-lambda-powertools-python/issues/852)) +* **deps:** bump actions/setup-python from 2.2.2 to 2.3.0 ([#831](https://github.com/awslabs/aws-lambda-powertools-python/issues/831)) +* **deps:** bump constructs from 10.1.1 to 10.1.66 ([#1414](https://github.com/awslabs/aws-lambda-powertools-python/issues/1414)) +* **deps:** bump aws-actions/configure-aws-credentials from 1 to 2 ([#1987](https://github.com/awslabs/aws-lambda-powertools-python/issues/1987)) +* **deps:** bump release-drafter/release-drafter from 5.21.1 to 5.22.0 ([#1802](https://github.com/awslabs/aws-lambda-powertools-python/issues/1802)) +* **deps:** bump boto3 from 1.18.61 to 1.19.6 ([#783](https://github.com/awslabs/aws-lambda-powertools-python/issues/783)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.1 to 2.0.3 ([#1801](https://github.com/awslabs/aws-lambda-powertools-python/issues/1801)) +* **deps:** bump aws-xray-sdk from 2.10.0 to 2.11.0 ([#1730](https://github.com/awslabs/aws-lambda-powertools-python/issues/1730)) +* **deps:** bump certifi from 2022.9.24 to 2022.12.7 ([#1768](https://github.com/awslabs/aws-lambda-powertools-python/issues/1768)) +* **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) +* **deps:** bump release-drafter/release-drafter from 5.20.0 to 5.20.1 ([#1458](https://github.com/awslabs/aws-lambda-powertools-python/issues/1458)) +* **deps:** bump boto3 from 1.20.3 to 1.20.5 ([#817](https://github.com/awslabs/aws-lambda-powertools-python/issues/817)) +* **deps:** bump boto3 from 1.19.6 to 1.20.3 ([#809](https://github.com/awslabs/aws-lambda-powertools-python/issues/809)) +* **deps:** bump release-drafter/release-drafter from 5.22.0 to 5.23.0 ([#1947](https://github.com/awslabs/aws-lambda-powertools-python/issues/1947)) +* **deps:** bump urllib3 from 1.26.4 to 1.26.5 ([#787](https://github.com/awslabs/aws-lambda-powertools-python/issues/787)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.4.0 to 2.0.1 ([#1752](https://github.com/awslabs/aws-lambda-powertools-python/issues/1752)) +* **deps-dev:** bump mypy-boto3-logs from 1.25.0 to 1.26.3 ([#1702](https://github.com/awslabs/aws-lambda-powertools-python/issues/1702)) +* **deps-dev:** bump flake8-eradicate from 1.1.0 to 1.2.0 ([#784](https://github.com/awslabs/aws-lambda-powertools-python/issues/784)) * **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#1481](https://github.com/awslabs/aws-lambda-powertools-python/issues/1481)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.49 to 1.26.89 ([#1996](https://github.com/awslabs/aws-lambda-powertools-python/issues/1996)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.0.post1 to 1.26.11.post1 ([#1746](https://github.com/awslabs/aws-lambda-powertools-python/issues/1746)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.0.post1 to 1.26.17 ([#1753](https://github.com/awslabs/aws-lambda-powertools-python/issues/1753)) +* **deps-dev:** bump flake8-black from 0.3.3 to 0.3.5 ([#1738](https://github.com/awslabs/aws-lambda-powertools-python/issues/1738)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.0.post1 to 1.26.12 ([#1742](https://github.com/awslabs/aws-lambda-powertools-python/issues/1742)) +* **deps-dev:** bump importlib-metadata from 4.13.0 to 5.1.0 ([#1750](https://github.com/awslabs/aws-lambda-powertools-python/issues/1750)) +* **deps-dev:** bump mkdocs-material from 8.5.10 to 8.5.11 ([#1756](https://github.com/awslabs/aws-lambda-powertools-python/issues/1756)) +* **deps-dev:** bump pytest-xdist from 3.0.2 to 3.1.0 ([#1758](https://github.com/awslabs/aws-lambda-powertools-python/issues/1758)) +* **deps-dev:** bump aws-cdk-lib from 2.50.0 to 2.51.1 ([#1741](https://github.com/awslabs/aws-lambda-powertools-python/issues/1741)) +* **deps-dev:** bump filelock from 3.8.0 to 3.8.2 ([#1759](https://github.com/awslabs/aws-lambda-powertools-python/issues/1759)) +* **deps-dev:** bump flake8-bugbear from 22.10.27 to 22.12.6 ([#1760](https://github.com/awslabs/aws-lambda-powertools-python/issues/1760)) +* **deps-dev:** bump aws-cdk-lib from 2.53.0 to 2.54.0 ([#1764](https://github.com/awslabs/aws-lambda-powertools-python/issues/1764)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.17 to 1.26.27 ([#1775](https://github.com/awslabs/aws-lambda-powertools-python/issues/1775)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.13.post16 to 1.26.24 ([#1765](https://github.com/awslabs/aws-lambda-powertools-python/issues/1765)) +* **deps-dev:** bump flake8-bugbear from 22.8.22 to 22.8.23 ([#1473](https://github.com/awslabs/aws-lambda-powertools-python/issues/1473)) +* **deps-dev:** bump flake8-isort from 4.0.0 to 4.1.1 ([#785](https://github.com/awslabs/aws-lambda-powertools-python/issues/785)) +* **deps-dev:** bump mkdocs-material from 7.3.3 to 7.3.5 ([#781](https://github.com/awslabs/aws-lambda-powertools-python/issues/781)) +* **deps-dev:** bump mkdocs-material from 7.3.5 to 7.3.6 ([#791](https://github.com/awslabs/aws-lambda-powertools-python/issues/791)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.0.post1 to 1.26.13.post16 ([#1743](https://github.com/awslabs/aws-lambda-powertools-python/issues/1743)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.0.post1 to 1.26.12 ([#1744](https://github.com/awslabs/aws-lambda-powertools-python/issues/1744)) * **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#306](https://github.com/awslabs/aws-lambda-powertools-python/issues/306)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.4 to 1.26.11.post1 ([#1740](https://github.com/awslabs/aws-lambda-powertools-python/issues/1740)) +* **deps-dev:** bump types-requests from 2.28.11.4 to 2.28.11.5 ([#1729](https://github.com/awslabs/aws-lambda-powertools-python/issues/1729)) * **deps-dev:** bump mkdocs-material from 8.4.1 to 8.4.2 ([#1483](https://github.com/awslabs/aws-lambda-powertools-python/issues/1483)) +* **deps-dev:** bump cfn-lint from 0.74.0 to 0.74.1 ([#1988](https://github.com/awslabs/aws-lambda-powertools-python/issues/1988)) +* **deps-dev:** bump coverage from 6.0.2 to 6.1.2 ([#810](https://github.com/awslabs/aws-lambda-powertools-python/issues/810)) +* **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) +* **deps-dev:** bump pytest-asyncio from 0.20.2 to 0.20.3 ([#1767](https://github.com/awslabs/aws-lambda-powertools-python/issues/1767)) +* **deps-dev:** bump isort from 5.10.1 to 5.11.2 ([#1782](https://github.com/awslabs/aws-lambda-powertools-python/issues/1782)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.17 to 1.26.30 ([#1785](https://github.com/awslabs/aws-lambda-powertools-python/issues/1785)) +* **deps-dev:** bump aws-cdk-lib from 2.54.0 to 2.55.1 ([#1787](https://github.com/awslabs/aws-lambda-powertools-python/issues/1787)) +* **deps-dev:** bump isort from 5.11.2 to 5.11.3 ([#1788](https://github.com/awslabs/aws-lambda-powertools-python/issues/1788)) +* **deps-dev:** bump types-requests from 2.28.11.5 to 2.28.11.7 ([#1795](https://github.com/awslabs/aws-lambda-powertools-python/issues/1795)) * **deps-dev:** revert to v1.28.0 dependencies +* **deps-dev:** bump flake8-black from 0.3.5 to 0.3.6 ([#1792](https://github.com/awslabs/aws-lambda-powertools-python/issues/1792)) +* **deps-dev:** bump black from 22.10.0 to 22.12.0 ([#1770](https://github.com/awslabs/aws-lambda-powertools-python/issues/1770)) +* **deps-dev:** bump isort from 5.9.3 to 5.10.1 ([#811](https://github.com/awslabs/aws-lambda-powertools-python/issues/811)) +* **deps-dev:** bump mkdocs-material from 8.5.9 to 8.5.10 ([#1731](https://github.com/awslabs/aws-lambda-powertools-python/issues/1731)) * **deps-dev:** bump mkdocs-material from 8.4.4 to 8.5.0 ([#1514](https://github.com/awslabs/aws-lambda-powertools-python/issues/1514)) -* **maintainers:** update release workflow link -* **maintenance:** add discord link to first PR and first issue ([#1493](https://github.com/awslabs/aws-lambda-powertools-python/issues/1493)) - - - -## [v1.28.0] - 2022-08-25 -## Bug Fixes - -* **ci:** calculate parallel jobs based on infrastructure needs ([#1475](https://github.com/awslabs/aws-lambda-powertools-python/issues/1475)) -* **ci:** del flake8 direct dep over py3.6 conflicts and docs failure -* **ci:** move from pip-tools to poetry on layers reusable workflow -* **ci:** move from pip-tools to poetry on layers to fix conflicts -* **ci:** typo and bust gh actions cache -* **ci:** use poetry to resolve layer deps; pip for CDK -* **ci:** disable poetry venv for layer workflow as cdk ignores venv -* **ci:** add cdk v2 dep for layers workflow -* **ci:** move from pip-tools to poetry on layers -* **ci:** temporarily disable changelog upon release -* **ci:** add explicit origin to fix release detached head -* **jmespath_util:** snappy as dev dep and typing example ([#1446](https://github.com/awslabs/aws-lambda-powertools-python/issues/1446)) - -## Documentation - -* **apigateway:** removes duplicate admonition ([#1426](https://github.com/awslabs/aws-lambda-powertools-python/issues/1426)) -* **home:** fix discord syntax and add Discord badge -* **home:** add discord invitation link ([#1471](https://github.com/awslabs/aws-lambda-powertools-python/issues/1471)) -* **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) -* **layer:** upgrade to 1.27.0 -* **layer:** upgrade to 1.27.0 -* **middleware-factory:** snippets split, improved, and lint ([#1451](https://github.com/awslabs/aws-lambda-powertools-python/issues/1451)) -* **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) -* **typing:** snippets split, improved, and lint ([#1465](https://github.com/awslabs/aws-lambda-powertools-python/issues/1465)) -* **validation:** snippets split, improved, and lint ([#1449](https://github.com/awslabs/aws-lambda-powertools-python/issues/1449)) - -## Features - -* **parser:** add support for Lambda Function URL ([#1442](https://github.com/awslabs/aws-lambda-powertools-python/issues/1442)) - -## Maintenance - -* **batch:** deprecate sqs_batch_processor ([#1463](https://github.com/awslabs/aws-lambda-powertools-python/issues/1463)) -* **ci:** prevent concurrent git update in critical workflows ([#1478](https://github.com/awslabs/aws-lambda-powertools-python/issues/1478)) -* **ci:** disable e2e py version matrix due to concurrent locking -* **ci:** revert e2e py version matrix -* **ci:** temp disable e2e matrix -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** reduce payload and only send prod notification -* **ci:** remove area/utilities conflicting label -* **ci:** include py version in stack and cache lock -* **ci:** remove conventional changelog commit to reduce noise -* **ci:** update changelog with latest changes -* **deps:** bump release-drafter/release-drafter from 5.20.0 to 5.20.1 ([#1458](https://github.com/awslabs/aws-lambda-powertools-python/issues/1458)) -* **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) -* **deps-dev:** bump flake8-bugbear from 22.8.22 to 22.8.23 ([#1473](https://github.com/awslabs/aws-lambda-powertools-python/issues/1473)) -* **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) -* **maintainer:** add Leandro as maintainer ([#1468](https://github.com/awslabs/aws-lambda-powertools-python/issues/1468)) -* **tests:** build and deploy Lambda Layer stack once ([#1466](https://github.com/awslabs/aws-lambda-powertools-python/issues/1466)) -* **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) -* **tests:** enable end-to-end test workflow ([#1470](https://github.com/awslabs/aws-lambda-powertools-python/issues/1470)) -* **tests:** refactor E2E logger to ease maintenance, writing tests and parallelization ([#1460](https://github.com/awslabs/aws-lambda-powertools-python/issues/1460)) -* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) - -## Reverts -* fix(ci): add explicit origin to fix release detached head - - - -## [v1.27.0] - 2022-08-05 -## Bug Fixes - -* **ci:** changelog workflow must receive git tags too -* **ci:** add additional input to accurately describe intent on skip -* **ci:** job permissions -* **event_sources:** add test for Function URL AuthZ ([#1421](https://github.com/awslabs/aws-lambda-powertools-python/issues/1421)) - -## Documentation - -* **layer:** upgrade to 1.26.7 - -## Features - -* **ci:** create reusable changelog generation ([#1418](https://github.com/awslabs/aws-lambda-powertools-python/issues/1418)) -* **ci:** include changelog generation on docs build -* **ci:** create reusable changelog generation -* **event_handlers:** Add support for Lambda Function URLs ([#1408](https://github.com/awslabs/aws-lambda-powertools-python/issues/1408)) -* **metrics:** update max user-defined dimensions from 9 to 29 ([#1417](https://github.com/awslabs/aws-lambda-powertools-python/issues/1417)) - -## Maintenance - -* **ci:** sync area labels to prevent dedup -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** add manual trigger for docs -* **ci:** update changelog with latest changes -* **ci:** temporarily disable changelog push on release -* **ci:** update changelog with latest changes -* **ci:** move changelog generation to rebuild_latest_doc workflow -* **ci:** update project with version -* **ci:** update release automated activities -* **ci:** readd changelog step on release -* **ci:** move changelog generation to rebuild_latest_doc workflow -* **ci:** drop 3.6 from workflows -* **deps:** bump constructs from 10.1.1 to 10.1.60 ([#1399](https://github.com/awslabs/aws-lambda-powertools-python/issues/1399)) -* **deps:** bump constructs from 10.1.1 to 10.1.66 ([#1414](https://github.com/awslabs/aws-lambda-powertools-python/issues/1414)) -* **deps:** bump jsii from 1.57.0 to 1.63.2 ([#1400](https://github.com/awslabs/aws-lambda-powertools-python/issues/1400)) -* **deps:** bump constructs from 10.1.1 to 10.1.64 ([#1405](https://github.com/awslabs/aws-lambda-powertools-python/issues/1405)) -* **deps:** bump attrs from 21.4.0 to 22.1.0 ([#1397](https://github.com/awslabs/aws-lambda-powertools-python/issues/1397)) -* **deps:** bump constructs from 10.1.1 to 10.1.63 ([#1402](https://github.com/awslabs/aws-lambda-powertools-python/issues/1402)) -* **deps:** bump constructs from 10.1.1 to 10.1.65 ([#1407](https://github.com/awslabs/aws-lambda-powertools-python/issues/1407)) -* **deps-dev:** bump types-requests from 2.28.5 to 2.28.6 ([#1401](https://github.com/awslabs/aws-lambda-powertools-python/issues/1401)) +* **deps-dev:** bump black from 21.12b0 to 22.8.0 ([#1515](https://github.com/awslabs/aws-lambda-powertools-python/issues/1515)) +* **deps-dev:** bump importlib-metadata from 5.1.0 to 6.0.0 ([#1804](https://github.com/awslabs/aws-lambda-powertools-python/issues/1804)) +* **deps-dev:** bump aws-cdk-lib from 2.55.1 to 2.59.0 ([#1810](https://github.com/awslabs/aws-lambda-powertools-python/issues/1810)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.60 to 1.24.74 ([#1522](https://github.com/awslabs/aws-lambda-powertools-python/issues/1522)) +* **deps-dev:** bump black from 21.10b0 to 21.11b1 ([#839](https://github.com/awslabs/aws-lambda-powertools-python/issues/839)) +* **deps-dev:** bump mkdocs-material from 8.5.0 to 8.5.1 ([#1521](https://github.com/awslabs/aws-lambda-powertools-python/issues/1521)) +* **deps-dev:** bump mypy-boto3-s3 from 1.24.36.post1 to 1.24.76 ([#1531](https://github.com/awslabs/aws-lambda-powertools-python/issues/1531)) * **deps-dev:** bump types-requests from 2.28.6 to 2.28.7 ([#1406](https://github.com/awslabs/aws-lambda-powertools-python/issues/1406)) -* **docs:** remove pause sentence from roadmap ([#1409](https://github.com/awslabs/aws-lambda-powertools-python/issues/1409)) -* **docs:** update site name to test ci changelog -* **docs:** update CHANGELOG for v1.26.7 -* **docs:** update description to trigger changelog generation -* **governance:** remove devcontainer in favour of gitpod.io ([#1411](https://github.com/awslabs/aws-lambda-powertools-python/issues/1411)) -* **governance:** add pre-configured dev environment with GitPod.io to ease contributions ([#1403](https://github.com/awslabs/aws-lambda-powertools-python/issues/1403)) -* **layers:** upgrade cdk dep hashes to prevent ci fail - - - -## [v1.26.7] - 2022-07-29 -## Bug Fixes - -* **ci:** add missing oidc token generation permission -* **event_handlers:** ImportError when importing Response from top-level event_handler ([#1388](https://github.com/awslabs/aws-lambda-powertools-python/issues/1388)) - -## Documentation - -* **examples:** enforce and fix all mypy errors ([#1393](https://github.com/awslabs/aws-lambda-powertools-python/issues/1393)) - -## Features - -* **idempotency:** handle lambda timeout scenarios for INPROGRESS records ([#1387](https://github.com/awslabs/aws-lambda-powertools-python/issues/1387)) - -## Maintenance - -* **ci:** increase skip_pypi logic to cover tests/changelog on re-run failures -* **ci:** update project with version 1.26.6 -* **ci:** drop 3.6 from workflows ([#1395](https://github.com/awslabs/aws-lambda-powertools-python/issues/1395)) -* **ci:** add conditional to skip pypi release ([#1366](https://github.com/awslabs/aws-lambda-powertools-python/issues/1366)) -* **ci:** remove leftover logic from on_merged_pr workflow -* **ci:** update project with version 1.26.6 -* **ci:** update project with version 1.26.6 -* **deps:** bump jsii from 1.57.0 to 1.63.1 ([#1390](https://github.com/awslabs/aws-lambda-powertools-python/issues/1390)) -* **deps:** bump constructs from 10.1.1 to 10.1.59 ([#1396](https://github.com/awslabs/aws-lambda-powertools-python/issues/1396)) -* **deps-dev:** bump flake8-isort from 4.1.1 to 4.1.2.post0 ([#1384](https://github.com/awslabs/aws-lambda-powertools-python/issues/1384)) -* **layers:** bump to 1.26.6 using layer v26 -* **maintainers:** add Ruben as a maintainer ([#1392](https://github.com/awslabs/aws-lambda-powertools-python/issues/1392)) - - - -## [v1.26.6] - 2022-07-25 -## Bug Fixes - -* **ci:** remove unsupported env in workflow_call -* **ci:** allow inherit secrets for reusable workflow -* **ci:** remove unused secret -* **ci:** label_related_issue unresolved var from history mixup -* **ci:** cond doesnt support two expr w/ env -* **ci:** only event is resolved in cond -* **ci:** unexpected symbol due to double quotes... -* **event_handlers:** handle lack of headers when using auto-compression feature ([#1325](https://github.com/awslabs/aws-lambda-powertools-python/issues/1325)) - -## Maintenance - -* dummy for PR test -* print full event depth -* print full workflow event depth -* debug full event -* remove leftover from fork one more time -* **ci:** test env expr -* **ci:** test upstream job skip -* **ci:** lockdown workflow_run by origin ([#1350](https://github.com/awslabs/aws-lambda-powertools-python/issues/1350)) -* **ci:** test default env -* **ci:** experiment hardening origin -* **ci:** experiment hardening origin -* **ci:** introduce codeowners ([#1352](https://github.com/awslabs/aws-lambda-powertools-python/issues/1352)) -* **ci:** use OIDC and encrypt release secrets ([#1355](https://github.com/awslabs/aws-lambda-powertools-python/issues/1355)) -* **ci:** remove core group from codeowners ([#1358](https://github.com/awslabs/aws-lambda-powertools-python/issues/1358)) -* **ci:** confirm workflow_run event -* **ci:** use gh environment for beta and prod layer deploy ([#1356](https://github.com/awslabs/aws-lambda-powertools-python/issues/1356)) -* **ci:** update project with version 1.26.5 -* **deps:** bump constructs from 10.1.1 to 10.1.52 ([#1343](https://github.com/awslabs/aws-lambda-powertools-python/issues/1343)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.24.0 to 1.24.35 ([#1342](https://github.com/awslabs/aws-lambda-powertools-python/issues/1342)) -* **governance:** update wording tech debt to summary in maintenance template -* **governance:** add new maintenance issue template for tech debt ([#1326](https://github.com/awslabs/aws-lambda-powertools-python/issues/1326)) -* **layers:** layer canary stack should not hardcode resource name -* **layers:** replace layers account secret ([#1329](https://github.com/awslabs/aws-lambda-powertools-python/issues/1329)) -* **layers:** expand to all aws commercial regions ([#1324](https://github.com/awslabs/aws-lambda-powertools-python/issues/1324)) -* **layers:** bump to 1.26.5 - -## Pull Requests - -* Merge pull request [#285](https://github.com/awslabs/aws-lambda-powertools-python/issues/285) from heitorlessa/chore/skip-dep-workflow -* Merge pull request [#284](https://github.com/awslabs/aws-lambda-powertools-python/issues/284) from heitorlessa/chore/dummy - - - -## [v1.26.5] - 2022-07-20 -## Bug Fixes - -* mathc the name of the cdk synth from the build phase -* typo in input for layer workflow -* no need to cache npm since we only install cdk cli and don't have .lock files -* add entire ARN role instead of account and role name -* path to artefact -* unzip the right artifact name -* download artefact into the layer dir -* sight, yes a whitespace character breaks the build -* **ci:** checkout project before validating related issue workflow -* **ci:** install poetry before calling setup/python with cache ([#1315](https://github.com/awslabs/aws-lambda-powertools-python/issues/1315)) -* **ci:** remove additional quotes in PR action ([#1317](https://github.com/awslabs/aws-lambda-powertools-python/issues/1317)) -* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) -* **ci:** fetch all git info so we can check tags -* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) -* **ci:** keep layer version permission ([#1318](https://github.com/awslabs/aws-lambda-powertools-python/issues/1318)) -* **ci:** regex to catch combination of related issues workflow -* **deps:** correct mypy types as dev dependency ([#1322](https://github.com/awslabs/aws-lambda-powertools-python/issues/1322)) -* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) - -## Documentation - -* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) -* **governance:** typos on PR template fixes [#1314](https://github.com/awslabs/aws-lambda-powertools-python/issues/1314) -* **governance:** add security doc to the root - -## Maintenance - -* **ci:** limits concurrency for docs workflow -* **ci:** adds caching when installing python dependencies ([#1311](https://github.com/awslabs/aws-lambda-powertools-python/issues/1311)) -* **ci:** update project with version 1.26.4 -* **ci:** fix reference error in related_issue -* **deps:** bump constructs from 10.1.1 to 10.1.51 ([#1323](https://github.com/awslabs/aws-lambda-powertools-python/issues/1323)) -* **deps-dev:** bump mypy from 0.961 to 0.971 ([#1320](https://github.com/awslabs/aws-lambda-powertools-python/issues/1320)) -* **governance:** fix typo on semantic commit link introduced in [#1](https://github.com/awslabs/aws-lambda-powertools-python/issues/1)aef4 -* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) -* **layers:** bump to 22 for 1.26.3 - - - -## [v1.26.4] - 2022-07-18 -## Bug Fixes - -* **ci:** checkout project before validating related issue workflow -* **ci:** fixes typos and small issues on github scripts ([#1302](https://github.com/awslabs/aws-lambda-powertools-python/issues/1302)) -* **ci:** address conditional type on_merge -* **ci:** address pr title semantic not found logic -* **ci:** address gh-actions additional quotes; remove debug -* **ci:** regex group name for on_merge workflow -* **ci:** escape outputs as certain PRs can break GH Actions expressions -* **ci:** move conditionals from yaml to code; leftover -* **ci:** move conditionals from yaml to code -* **ci:** accept core arg in label related issue workflow -* **ci:** match the name of the cdk synth from the build phase -* **ci:** regex to catch combination of related issues workflow -* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) -* **parser:** raise ValidationError when SNS->SQS keys are intentionally missing ([#1299](https://github.com/awslabs/aws-lambda-powertools-python/issues/1299)) - -## Documentation - -* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) -* **graphql:** snippets split, improved, and lint ([#1287](https://github.com/awslabs/aws-lambda-powertools-python/issues/1287)) -* **homepage:** emphasize additional powertools languages ([#1292](https://github.com/awslabs/aws-lambda-powertools-python/issues/1292)) -* **metrics:** snippets split, improved, and lint - -## Maintenance - -* **ci:** increase release automation and limit to one manual step ([#1297](https://github.com/awslabs/aws-lambda-powertools-python/issues/1297)) -* **ci:** make export PR reusable -* **ci:** auto-merge cdk lib and lambda layer construct -* **ci:** convert inline gh-script to file -* **ci:** lockdown 3rd party workflows to pin sha ([#1301](https://github.com/awslabs/aws-lambda-powertools-python/issues/1301)) -* **ci:** automatically add area label based on title ([#1300](https://github.com/awslabs/aws-lambda-powertools-python/issues/1300)) -* **ci:** disable output debugging as pr body isnt accepted -* **ci:** experiment with conditional on outputs -* **ci:** improve error handling for non-issue numbers -* **ci:** add end to end testing mechanism ([#1247](https://github.com/awslabs/aws-lambda-powertools-python/issues/1247)) -* **ci:** limits concurrency for docs workflow -* **ci:** fix reference error in related_issue -* **ci:** move error prone env to code as constants -* **ci:** move all scripts under .github/scripts -* **deps:** bump cdk-lambda-powertools-python-layer ([#1284](https://github.com/awslabs/aws-lambda-powertools-python/issues/1284)) -* **deps:** bump jsii from 1.61.0 to 1.62.0 ([#1294](https://github.com/awslabs/aws-lambda-powertools-python/issues/1294)) -* **deps:** bump constructs from 10.1.1 to 10.1.46 ([#1306](https://github.com/awslabs/aws-lambda-powertools-python/issues/1306)) -* **deps:** bump actions/setup-node from 2 to 3 ([#1281](https://github.com/awslabs/aws-lambda-powertools-python/issues/1281)) -* **deps:** bump fastjsonschema from 2.15.3 to 2.16.1 ([#1309](https://github.com/awslabs/aws-lambda-powertools-python/issues/1309)) -* **deps:** bump constructs from 10.1.1 to 10.1.49 ([#1308](https://github.com/awslabs/aws-lambda-powertools-python/issues/1308)) -* **deps:** bump attrs from 21.2.0 to 21.4.0 ([#1282](https://github.com/awslabs/aws-lambda-powertools-python/issues/1282)) -* **deps:** bump aws-cdk-lib from 2.29.0 to 2.31.1 ([#1290](https://github.com/awslabs/aws-lambda-powertools-python/issues/1290)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.12 to 1.24.27 ([#1293](https://github.com/awslabs/aws-lambda-powertools-python/issues/1293)) +* **deps-dev:** bump mkdocs-material from 8.5.1 to 8.5.3 ([#1532](https://github.com/awslabs/aws-lambda-powertools-python/issues/1532)) +* **deps-dev:** bump pytest-asyncio from 0.15.1 to 0.16.0 ([#782](https://github.com/awslabs/aws-lambda-powertools-python/issues/782)) +* **deps-dev:** bump types-requests from 2.28.10 to 2.28.11 ([#1538](https://github.com/awslabs/aws-lambda-powertools-python/issues/1538)) +* **deps-dev:** bump types-requests from 2.28.5 to 2.28.6 ([#1401](https://github.com/awslabs/aws-lambda-powertools-python/issues/1401)) +* **deps-dev:** bump mako from 1.2.2 to 1.2.3 ([#1537](https://github.com/awslabs/aws-lambda-powertools-python/issues/1537)) +* **deps-dev:** bump mypy-boto3-ssm from 1.24.69 to 1.24.80 ([#1542](https://github.com/awslabs/aws-lambda-powertools-python/issues/1542)) +* **deps-dev:** bump isort from 5.11.3 to 5.11.4 ([#1809](https://github.com/awslabs/aws-lambda-powertools-python/issues/1809)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.12 to 1.26.40 ([#1811](https://github.com/awslabs/aws-lambda-powertools-python/issues/1811)) +* **deps-dev:** bump mypy-boto3-ssm from 1.24.80 to 1.24.81 ([#1544](https://github.com/awslabs/aws-lambda-powertools-python/issues/1544)) +* **deps-dev:** bump flake8 from 3.9.2 to 4.0.1 ([#789](https://github.com/awslabs/aws-lambda-powertools-python/issues/789)) +* **deps-dev:** bump flake8-bugbear from 22.9.11 to 22.9.23 ([#1541](https://github.com/awslabs/aws-lambda-powertools-python/issues/1541)) +* **deps-dev:** bump flake8-builtins from 2.0.1 to 2.1.0 ([#1799](https://github.com/awslabs/aws-lambda-powertools-python/issues/1799)) +* **deps-dev:** bump pytest-cov from 3.0.0 to 4.0.0 ([#1551](https://github.com/awslabs/aws-lambda-powertools-python/issues/1551)) +* **deps-dev:** bump coverage from 6.0.1 to 6.0.2 ([#764](https://github.com/awslabs/aws-lambda-powertools-python/issues/764)) +* **deps-dev:** bump flake8-isort from 4.1.1 to 4.1.2.post0 ([#1384](https://github.com/awslabs/aws-lambda-powertools-python/issues/1384)) +* **deps-dev:** bump coverage from 6.5.0 to 7.0.3 ([#1806](https://github.com/awslabs/aws-lambda-powertools-python/issues/1806)) +* **deps-dev:** bump mkdocs-material from 8.5.11 to 9.0.2 ([#1808](https://github.com/awslabs/aws-lambda-powertools-python/issues/1808)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.24.54 to 1.24.83 ([#1557](https://github.com/awslabs/aws-lambda-powertools-python/issues/1557)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.11.post1 to 1.26.35.post1 ([#1818](https://github.com/awslabs/aws-lambda-powertools-python/issues/1818)) +* **deps-dev:** bump black from 21.11b1 to 21.12b0 ([#872](https://github.com/awslabs/aws-lambda-powertools-python/issues/872)) +* **deps-dev:** bump filelock from 3.8.2 to 3.9.0 ([#1816](https://github.com/awslabs/aws-lambda-powertools-python/issues/1816)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.27 to 1.26.43 ([#1820](https://github.com/awslabs/aws-lambda-powertools-python/issues/1820)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.11.post1 to 1.26.43 ([#1819](https://github.com/awslabs/aws-lambda-powertools-python/issues/1819)) +* **deps-dev:** bump mkdocs-material from 8.5.3 to 8.5.4 ([#1563](https://github.com/awslabs/aws-lambda-powertools-python/issues/1563)) +* **deps-dev:** bump types-requests from 2.28.11 to 2.28.11.1 ([#1571](https://github.com/awslabs/aws-lambda-powertools-python/issues/1571)) +* **deps-dev:** bump ijson from 3.1.4 to 3.2.0.post0 ([#1815](https://github.com/awslabs/aws-lambda-powertools-python/issues/1815)) +* **deps-dev:** bump aws-cdk-lib from 2.67.0 to 2.68.0 ([#1992](https://github.com/awslabs/aws-lambda-powertools-python/issues/1992)) +* **deps-dev:** bump coverage from 7.0.3 to 7.0.4 ([#1822](https://github.com/awslabs/aws-lambda-powertools-python/issues/1822)) +* **deps-dev:** bump mkdocs-material from 9.0.2 to 9.0.3 ([#1823](https://github.com/awslabs/aws-lambda-powertools-python/issues/1823)) +* **deps-dev:** bump coverage from 7.0.4 to 7.0.5 ([#1829](https://github.com/awslabs/aws-lambda-powertools-python/issues/1829)) +* **deps-dev:** bump aws-cdk-lib from 2.59.0 to 2.60.0 ([#1831](https://github.com/awslabs/aws-lambda-powertools-python/issues/1831)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.18 to 1.26.49 ([#1832](https://github.com/awslabs/aws-lambda-powertools-python/issues/1832)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.40 to 1.26.49 ([#1835](https://github.com/awslabs/aws-lambda-powertools-python/issues/1835)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.43 to 1.26.49 ([#1834](https://github.com/awslabs/aws-lambda-powertools-python/issues/1834)) +* **deps-dev:** bump mkdocs-material from 9.0.3 to 9.0.4 ([#1833](https://github.com/awslabs/aws-lambda-powertools-python/issues/1833)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.84 to 1.26.87 ([#1993](https://github.com/awslabs/aws-lambda-powertools-python/issues/1993)) +* **deps-dev:** bump typing-extensions from 4.3.0 to 4.4.0 ([#1575](https://github.com/awslabs/aws-lambda-powertools-python/issues/1575)) +* **deps-dev:** bump mkdocs-material from 9.0.4 to 9.0.5 ([#1840](https://github.com/awslabs/aws-lambda-powertools-python/issues/1840)) +* **deps-dev:** bump pytest from 7.2.0 to 7.2.1 ([#1838](https://github.com/awslabs/aws-lambda-powertools-python/issues/1838)) +* **deps-dev:** bump types-requests from 2.28.11.1 to 2.28.11.2 ([#1576](https://github.com/awslabs/aws-lambda-powertools-python/issues/1576)) +* **deps-dev:** bump types-requests from 2.28.11.7 to 2.28.11.8 ([#1843](https://github.com/awslabs/aws-lambda-powertools-python/issues/1843)) +* **deps-dev:** bump mypy-boto3-xray from 1.26.9 to 1.26.11.post1 ([#1734](https://github.com/awslabs/aws-lambda-powertools-python/issues/1734)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.24.0 to 1.24.35 ([#1342](https://github.com/awslabs/aws-lambda-powertools-python/issues/1342)) +* **deps-dev:** bump pytest from 7.2.1 to 7.2.2 ([#1980](https://github.com/awslabs/aws-lambda-powertools-python/issues/1980)) +* **deps-dev:** bump mkdocs-material from 7.3.2 to 7.3.3 ([#758](https://github.com/awslabs/aws-lambda-powertools-python/issues/758)) +* **deps-dev:** bump mypy from 0.910 to 0.920 ([#903](https://github.com/awslabs/aws-lambda-powertools-python/issues/903)) +* **deps-dev:** bump flake8-builtins from 1.5.3 to 2.0.0 ([#1582](https://github.com/awslabs/aws-lambda-powertools-python/issues/1582)) +* **deps-dev:** bump mypy-boto3-ssm from 1.24.81 to 1.24.90 ([#1594](https://github.com/awslabs/aws-lambda-powertools-python/issues/1594)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.9 to 2.8.19.10 ([#1973](https://github.com/awslabs/aws-lambda-powertools-python/issues/1973)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.30 to 1.26.52 ([#1847](https://github.com/awslabs/aws-lambda-powertools-python/issues/1847)) +* **deps-dev:** bump flake8-comprehensions from 3.6.1 to 3.7.0 ([#759](https://github.com/awslabs/aws-lambda-powertools-python/issues/759)) +* **deps-dev:** bump mypy from 0.961 to 0.971 ([#1320](https://github.com/awslabs/aws-lambda-powertools-python/issues/1320)) +* **deps-dev:** bump mypy from 0.920 to 0.930 ([#925](https://github.com/awslabs/aws-lambda-powertools-python/issues/925)) +* **deps-dev:** bump aws-cdk-lib from 2.60.0 to 2.61.1 ([#1849](https://github.com/awslabs/aws-lambda-powertools-python/issues/1849)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.49 to 1.26.53 ([#1850](https://github.com/awslabs/aws-lambda-powertools-python/issues/1850)) +* **deps-dev:** bump mkdocs-material from 9.0.5 to 9.0.6 ([#1851](https://github.com/awslabs/aws-lambda-powertools-python/issues/1851)) +* **deps-dev:** bump flake8-builtins from 2.0.0 to 2.0.1 ([#1715](https://github.com/awslabs/aws-lambda-powertools-python/issues/1715)) +* **deps-dev:** bump pytest-asyncio from 0.20.1 to 0.20.2 ([#1723](https://github.com/awslabs/aws-lambda-powertools-python/issues/1723)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.25.0 to 1.26.0.post1 ([#1722](https://github.com/awslabs/aws-lambda-powertools-python/issues/1722)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.49 to 1.26.55 ([#1856](https://github.com/awslabs/aws-lambda-powertools-python/issues/1856)) +* **deps-dev:** bump mypy-boto3-s3 from 1.24.76 to 1.24.94 ([#1622](https://github.com/awslabs/aws-lambda-powertools-python/issues/1622)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.0.post1 to 1.26.4 ([#1721](https://github.com/awslabs/aws-lambda-powertools-python/issues/1721)) +* **deps-dev:** bump aws-cdk-lib from 2.61.1 to 2.62.0 ([#1863](https://github.com/awslabs/aws-lambda-powertools-python/issues/1863)) +* **deps-dev:** bump coverage from 7.0.5 to 7.1.0 ([#1862](https://github.com/awslabs/aws-lambda-powertools-python/issues/1862)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.35.post1 to 1.26.57 ([#1865](https://github.com/awslabs/aws-lambda-powertools-python/issues/1865)) * **deps-dev:** bump mypy-boto3-appconfig from 1.24.0 to 1.24.29 ([#1295](https://github.com/awslabs/aws-lambda-powertools-python/issues/1295)) -* **governance:** remove any step relying on master branch -* **governance:** update emeritus affiliation -* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) -* **layers:** bump to 22 for 1.26.3 - - - -## [v1.26.3] - 2022-07-04 -## Bug Fixes - -* **ci:** remove utf-8 body in octokit body req -* **ci:** improve msg visibility on closed issues -* **ci:** disable merged_pr workflow -* **ci:** merged_pr add issues write access -* **ci:** quote prBody GH expr on_opened_pr -* **ci:** reusable workflow secrets param -* **logger:** support additional args for handlers when injecting lambda context ([#1276](https://github.com/awslabs/aws-lambda-powertools-python/issues/1276)) -* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) - -## Documentation - -* **lint:** add markdownlint rules and automation ([#1256](https://github.com/awslabs/aws-lambda-powertools-python/issues/1256)) -* **logger:** document enriching logs with logrecord attributes ([#1271](https://github.com/awslabs/aws-lambda-powertools-python/issues/1271)) -* **logger:** snippets split, improved, and lint ([#1262](https://github.com/awslabs/aws-lambda-powertools-python/issues/1262)) -* **metrics:** snippets split, improved, and lint ([#1272](https://github.com/awslabs/aws-lambda-powertools-python/issues/1272)) -* **tracer:** snippets split, improved, and lint ([#1261](https://github.com/awslabs/aws-lambda-powertools-python/issues/1261)) -* **tracer:** split and lint code snippets ([#1260](https://github.com/awslabs/aws-lambda-powertools-python/issues/1260)) - -## Maintenance - -* move to approach B for multiple IaC -* add sam build gitignore -* bump to version 1.26.3 -* **ci:** reactivate on_merged_pr workflow -* **ci:** improve wording on closed issues action -* **ci:** deactivate on_merged_pr workflow -* **deps:** bump aws-xray-sdk from 2.9.0 to 2.10.0 ([#1270](https://github.com/awslabs/aws-lambda-powertools-python/issues/1270)) -* **deps:** bump dependabot/fetch-metadata from 1.1.1 to 1.3.2 ([#1269](https://github.com/awslabs/aws-lambda-powertools-python/issues/1269)) -* **deps:** bump dependabot/fetch-metadata from 1.3.2 to 1.3.3 ([#1273](https://github.com/awslabs/aws-lambda-powertools-python/issues/1273)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.12 to 1.24.27 ([#1293](https://github.com/awslabs/aws-lambda-powertools-python/issues/1293)) +* **deps-dev:** bump aws-cdk-lib from 2.46.0 to 2.47.0 ([#1629](https://github.com/awslabs/aws-lambda-powertools-python/issues/1629)) +* **deps-dev:** bump mypy-boto3-xray from 1.26.0.post1 to 1.26.9 ([#1720](https://github.com/awslabs/aws-lambda-powertools-python/issues/1720)) +* **deps-dev:** bump mypy-boto3-lambda from 1.25.0 to 1.26.0.post1 ([#1705](https://github.com/awslabs/aws-lambda-powertools-python/issues/1705)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.0.post1 to 1.26.58 ([#1868](https://github.com/awslabs/aws-lambda-powertools-python/issues/1868)) +* **deps-dev:** bump hvac from 1.0.2 to 1.1.0 ([#1983](https://github.com/awslabs/aws-lambda-powertools-python/issues/1983)) +* **deps-dev:** bump mypy-boto3-s3 from 1.25.0 to 1.26.0.post1 ([#1716](https://github.com/awslabs/aws-lambda-powertools-python/issues/1716)) +* **deps-dev:** bump aws-cdk-lib from 2.62.1 to 2.62.2 ([#1869](https://github.com/awslabs/aws-lambda-powertools-python/issues/1869)) +* **deps-dev:** bump mkdocs-material from 9.1.0 to 9.1.1 ([#1984](https://github.com/awslabs/aws-lambda-powertools-python/issues/1984)) +* **deps-dev:** bump isort from 5.11.4 to 5.11.5 ([#1875](https://github.com/awslabs/aws-lambda-powertools-python/issues/1875)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.25.0 to 1.26.0.post1 ([#1704](https://github.com/awslabs/aws-lambda-powertools-python/issues/1704)) +* **deps-dev:** bump mkdocs-material from 9.0.6 to 9.0.8 ([#1874](https://github.com/awslabs/aws-lambda-powertools-python/issues/1874)) +* **deps-dev:** bump flake8-bugbear from 22.12.6 to 23.1.20 ([#1854](https://github.com/awslabs/aws-lambda-powertools-python/issues/1854)) +* **deps-dev:** bump aws-cdk-lib from 2.62.2 to 2.63.0 ([#1887](https://github.com/awslabs/aws-lambda-powertools-python/issues/1887)) +* **deps-dev:** bump mypy from 0.930 to 0.931 ([#941](https://github.com/awslabs/aws-lambda-powertools-python/issues/941)) +* **deps-dev:** bump black from 22.12.0 to 23.1.0 ([#1886](https://github.com/awslabs/aws-lambda-powertools-python/issues/1886)) +* **deps-dev:** bump mypy-boto3-xray from 1.25.0 to 1.26.0.post1 ([#1703](https://github.com/awslabs/aws-lambda-powertools-python/issues/1703)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.58 to 1.26.62 ([#1889](https://github.com/awslabs/aws-lambda-powertools-python/issues/1889)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.24 to 1.26.84 ([#1981](https://github.com/awslabs/aws-lambda-powertools-python/issues/1981)) +* **deps-dev:** bump mkdocs-material from 9.0.9 to 9.0.10 ([#1888](https://github.com/awslabs/aws-lambda-powertools-python/issues/1888)) +* **deps-dev:** bump mkdocs-material from 9.0.15 to 9.1.0 ([#1976](https://github.com/awslabs/aws-lambda-powertools-python/issues/1976)) +* **deps-dev:** bump cfn-lint from 0.67.0 to 0.74.0 ([#1974](https://github.com/awslabs/aws-lambda-powertools-python/issues/1974)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.26.0.post1 to 1.26.63 ([#1895](https://github.com/awslabs/aws-lambda-powertools-python/issues/1895)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.25.0 to 1.26.0.post1 ([#1714](https://github.com/awslabs/aws-lambda-powertools-python/issues/1714)) * **deps-dev:** bump flake8-bugbear from 22.6.22 to 22.7.1 ([#1274](https://github.com/awslabs/aws-lambda-powertools-python/issues/1274)) +* **deps-dev:** bump pytest-asyncio from 0.16.0 to 0.20.1 ([#1635](https://github.com/awslabs/aws-lambda-powertools-python/issues/1635)) +* **deps-dev:** bump flake8-variables-names from 0.0.4 to 0.0.5 ([#1628](https://github.com/awslabs/aws-lambda-powertools-python/issues/1628)) +* **deps-dev:** bump aws-cdk-lib from 2.66.1 to 2.67.0 ([#1977](https://github.com/awslabs/aws-lambda-powertools-python/issues/1977)) +* **deps-dev:** bump aws-cdk-lib from 2.63.0 to 2.63.2 ([#1904](https://github.com/awslabs/aws-lambda-powertools-python/issues/1904)) +* **deps-dev:** bump aws-cdk-lib from 2.47.0 to 2.48.0 ([#1664](https://github.com/awslabs/aws-lambda-powertools-python/issues/1664)) +* **deps-dev:** bump pytest-xdist from 3.1.0 to 3.2.0 ([#1905](https://github.com/awslabs/aws-lambda-powertools-python/issues/1905)) * **deps-dev:** bump flake8-bugbear from 22.4.25 to 22.6.22 ([#1258](https://github.com/awslabs/aws-lambda-powertools-python/issues/1258)) * **deps-dev:** bump mypy-boto3-dynamodb from 1.24.0 to 1.24.12 ([#1255](https://github.com/awslabs/aws-lambda-powertools-python/issues/1255)) * **deps-dev:** bump mypy-boto3-secretsmanager ([#1252](https://github.com/awslabs/aws-lambda-powertools-python/issues/1252)) -* **governance:** fix on_merged_pr workflow syntax -* **governance:** warn message on closed issues -* **layers:** bump to 21 for 1.26.2 -* **test-perf:** use pytest-benchmark to improve reliability ([#1250](https://github.com/awslabs/aws-lambda-powertools-python/issues/1250)) - - - -## [v1.26.2] - 2022-06-16 -## Bug Fixes - -* **event-handler:** body to empty string in CORS preflight (ALB non-compliant) ([#1249](https://github.com/awslabs/aws-lambda-powertools-python/issues/1249)) - -## Code Refactoring - -* rename to clear_state -* rename to remove_custom_keys - -## Documentation - -* fix anchor - -## Features - -* **logger:** add option to clear state per invocation - -## Maintenance - -* bump to 1.26.2 -* **deps:** bump actions/setup-python from 3 to 4 ([#1244](https://github.com/awslabs/aws-lambda-powertools-python/issues/1244)) -* **deps-dev:** bump mypy from 0.960 to 0.961 ([#1241](https://github.com/awslabs/aws-lambda-powertools-python/issues/1241)) -* **deps-dev:** bump mypy-boto3-ssm from 1.23.0.post1 to 1.24.0 ([#1231](https://github.com/awslabs/aws-lambda-powertools-python/issues/1231)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.8 to 1.24.0 ([#1232](https://github.com/awslabs/aws-lambda-powertools-python/issues/1232)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.23.0.post1 to 1.24.0 ([#1234](https://github.com/awslabs/aws-lambda-powertools-python/issues/1234)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.23.0.post1 to 1.24.0 ([#1233](https://github.com/awslabs/aws-lambda-powertools-python/issues/1233)) -* **governance:** auto-merge on all PR events -* **governance:** add release label on pr merge -* **governance:** enforce safe scope on pr merge labelling -* **governance:** limit build workflow to code changes only -* **governance:** auto-merge workflow_dispatch off -* **governance:** auto-merge to use squash -* **governance:** check for related issue in new PRs -* **governance:** auto-merge mypy-stub dependabot -* **governance:** address gh reusable workflow limitation -* **governance:** fix workflow action requirements & syntax -* **governance:** warn message on closed issues -* **metrics:** revert dimensions test before splitting ([#1243](https://github.com/awslabs/aws-lambda-powertools-python/issues/1243)) - - - -## [v1.26.1] - 2022-06-07 -## Bug Fixes - -* **metrics:** raise SchemaValidationError for >8 metric dimensions ([#1240](https://github.com/awslabs/aws-lambda-powertools-python/issues/1240)) - -## Documentation - -* **governance:** link roadmap and maintainers doc -* **maintainers:** initial maintainers playbook ([#1222](https://github.com/awslabs/aws-lambda-powertools-python/issues/1222)) -* **roadmap:** use pinned pause issue instead - -## Maintenance - -* bump version 1.26.1 -* **deps-dev:** bump mypy from 0.950 to 0.960 ([#1224](https://github.com/awslabs/aws-lambda-powertools-python/issues/1224)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.0.post1 to 1.23.8 ([#1225](https://github.com/awslabs/aws-lambda-powertools-python/issues/1225)) - - - -## [v1.26.0] - 2022-05-20 -## Bug Fixes - -* **batch:** missing space in BatchProcessingError message ([#1201](https://github.com/awslabs/aws-lambda-powertools-python/issues/1201)) -* **batch:** docstring fix for success_handler() record parameter ([#1202](https://github.com/awslabs/aws-lambda-powertools-python/issues/1202)) -* **docs:** remove Slack link ([#1210](https://github.com/awslabs/aws-lambda-powertools-python/issues/1210)) - -## Documentation - -* **layer:** upgrade to 1.25.10 -* **roadmap:** add new roadmap section ([#1204](https://github.com/awslabs/aws-lambda-powertools-python/issues/1204)) - -## Features - -* **parameters:** accept boto3_client to support private endpoints and ease testing ([#1096](https://github.com/awslabs/aws-lambda-powertools-python/issues/1096)) - -## Maintenance - -* bump to 1.26.0 -* **deps:** bump pydantic from 1.9.0 to 1.9.1 ([#1221](https://github.com/awslabs/aws-lambda-powertools-python/issues/1221)) -* **deps:** bump email-validator from 1.1.3 to 1.2.1 ([#1199](https://github.com/awslabs/aws-lambda-powertools-python/issues/1199)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.21.34 to 1.23.0.post1 ([#1218](https://github.com/awslabs/aws-lambda-powertools-python/issues/1218)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.21.34 to 1.23.0.post1 ([#1219](https://github.com/awslabs/aws-lambda-powertools-python/issues/1219)) -* **deps-dev:** bump mypy-boto3-ssm from 1.21.34 to 1.23.0.post1 ([#1220](https://github.com/awslabs/aws-lambda-powertools-python/issues/1220)) - - - -## [v1.25.10] - 2022-04-29 -## Bug Fixes - -* **data-classes:** Add missing SES fields and ([#1045](https://github.com/awslabs/aws-lambda-powertools-python/issues/1045)) -* **deps:** Ignore boto3 changes until needed ([#1151](https://github.com/awslabs/aws-lambda-powertools-python/issues/1151)) -* **deps-dev:** remove jmespath due to dev deps conflict ([#1148](https://github.com/awslabs/aws-lambda-powertools-python/issues/1148)) -* **event_handler:** exception_handler to handle ServiceError exceptions ([#1160](https://github.com/awslabs/aws-lambda-powertools-python/issues/1160)) -* **event_handler:** Allow for event_source support ([#1159](https://github.com/awslabs/aws-lambda-powertools-python/issues/1159)) -* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) - -## Documentation - -* **layer:** upgrade to 1.25.9 - -## Features - -* **parameters:** add clear_cache method for providers ([#1194](https://github.com/awslabs/aws-lambda-powertools-python/issues/1194)) - -## Maintenance - -* include regression in changelog -* bump to 1.25.10 -* **ci:** changelog pre-generation to fetch tags from origin -* **ci:** disable mergify configuration after breaking changes ([#1188](https://github.com/awslabs/aws-lambda-powertools-python/issues/1188)) -* **ci:** post release on tagged issues too -* **deps:** bump codecov/codecov-action from 3.0.0 to 3.1.0 ([#1143](https://github.com/awslabs/aws-lambda-powertools-python/issues/1143)) -* **deps:** bump github/codeql-action from 1 to 2 ([#1154](https://github.com/awslabs/aws-lambda-powertools-python/issues/1154)) -* **deps-dev:** bump flake8-eradicate from 1.2.0 to 1.2.1 ([#1158](https://github.com/awslabs/aws-lambda-powertools-python/issues/1158)) -* **deps-dev:** bump mypy from 0.942 to 0.950 ([#1162](https://github.com/awslabs/aws-lambda-powertools-python/issues/1162)) -* **deps-dev:** bump mkdocs-git-revision-date-plugin ([#1146](https://github.com/awslabs/aws-lambda-powertools-python/issues/1146)) +* **deps-dev:** bump aws-cdk-lib from 2.62.0 to 2.62.1 ([#1866](https://github.com/awslabs/aws-lambda-powertools-python/issues/1866)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.55 to 1.26.80 ([#1967](https://github.com/awslabs/aws-lambda-powertools-python/issues/1967)) +* **deps-dev:** bump aws-cdk-lib from 2.48.0 to 2.49.0 ([#1671](https://github.com/awslabs/aws-lambda-powertools-python/issues/1671)) +* **deps-dev:** bump types-requests from 2.28.11.8 to 2.28.11.12 ([#1906](https://github.com/awslabs/aws-lambda-powertools-python/issues/1906)) +* **deps-dev:** bump mkdocs-material from 9.0.14 to 9.0.15 ([#1959](https://github.com/awslabs/aws-lambda-powertools-python/issues/1959)) +* **deps-dev:** bump flake8-bugbear from 21.11.29 to 22.1.11 ([#955](https://github.com/awslabs/aws-lambda-powertools-python/issues/955)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.8 to 2.8.19.9 ([#1960](https://github.com/awslabs/aws-lambda-powertools-python/issues/1960)) +* **deps-dev:** bump coverage from 6.0 to 6.0.1 ([#751](https://github.com/awslabs/aws-lambda-powertools-python/issues/751)) +* **deps-dev:** bump coverage from 7.2.0 to 7.2.1 ([#1963](https://github.com/awslabs/aws-lambda-powertools-python/issues/1963)) +* **deps-dev:** bump types-requests from 2.28.11.14 to 2.28.11.15 ([#1962](https://github.com/awslabs/aws-lambda-powertools-python/issues/1962)) +* **deps-dev:** bump mkdocs-material from 9.1.1 to 9.1.2 ([#1994](https://github.com/awslabs/aws-lambda-powertools-python/issues/1994)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.23.0.post1 to 1.24.0 ([#1233](https://github.com/awslabs/aws-lambda-powertools-python/issues/1233)) +* **deps-dev:** bump aws-cdk-lib from 2.66.0 to 2.66.1 ([#1954](https://github.com/awslabs/aws-lambda-powertools-python/issues/1954)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.23.0.post1 to 1.24.0 ([#1234](https://github.com/awslabs/aws-lambda-powertools-python/issues/1234)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.25.0 to 1.26.0.post1 ([#1679](https://github.com/awslabs/aws-lambda-powertools-python/issues/1679)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.25.0 to 1.26.0.post1 ([#1682](https://github.com/awslabs/aws-lambda-powertools-python/issues/1682)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.8 to 1.24.0 ([#1232](https://github.com/awslabs/aws-lambda-powertools-python/issues/1232)) +* **deps-dev:** bump aws-cdk-lib from 2.49.0 to 2.50.0 ([#1683](https://github.com/awslabs/aws-lambda-powertools-python/issues/1683)) +* **deps-dev:** bump mypy-boto3-ssm from 1.23.0.post1 to 1.24.0 ([#1231](https://github.com/awslabs/aws-lambda-powertools-python/issues/1231)) +* **deps-dev:** bump mypy from 0.960 to 0.961 ([#1241](https://github.com/awslabs/aws-lambda-powertools-python/issues/1241)) +* **deps-dev:** bump types-requests from 2.28.11.3 to 2.28.11.4 ([#1701](https://github.com/awslabs/aws-lambda-powertools-python/issues/1701)) +* **deps-dev:** bump mkdocs-material from 8.1.9 to 8.2.4 ([#1054](https://github.com/awslabs/aws-lambda-powertools-python/issues/1054)) +* **deps-dev:** bump coverage from 7.1.0 to 7.2.0 ([#1951](https://github.com/awslabs/aws-lambda-powertools-python/issues/1951)) +* **deps-dev:** bump mkdocs-material from 9.0.13 to 9.0.14 ([#1952](https://github.com/awslabs/aws-lambda-powertools-python/issues/1952)) +* **deps-dev:** bump mypy from 0.950 to 0.960 ([#1224](https://github.com/awslabs/aws-lambda-powertools-python/issues/1224)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.0.post1 to 1.23.8 ([#1225](https://github.com/awslabs/aws-lambda-powertools-python/issues/1225)) +* **deps-dev:** bump aws-cdk-lib from 2.63.2 to 2.64.0 ([#1918](https://github.com/awslabs/aws-lambda-powertools-python/issues/1918)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.21.34 to 1.23.0.post1 ([#1218](https://github.com/awslabs/aws-lambda-powertools-python/issues/1218)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.21.34 to 1.23.0.post1 ([#1219](https://github.com/awslabs/aws-lambda-powertools-python/issues/1219)) +* **deps-dev:** bump mypy-boto3-ssm from 1.21.34 to 1.23.0.post1 ([#1220](https://github.com/awslabs/aws-lambda-powertools-python/issues/1220)) +* **deps-dev:** bump mypy-boto3-ssm from 1.25.0 to 1.26.0.post1 ([#1690](https://github.com/awslabs/aws-lambda-powertools-python/issues/1690)) +* **deps-dev:** bump flake8-bugbear from 22.10.25 to 22.10.27 ([#1665](https://github.com/awslabs/aws-lambda-powertools-python/issues/1665)) +* **deps-dev:** bump mkdocs-material from 9.0.11 to 9.0.12 ([#1919](https://github.com/awslabs/aws-lambda-powertools-python/issues/1919)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.26.0.post1 to 1.26.70 ([#1925](https://github.com/awslabs/aws-lambda-powertools-python/issues/1925)) +* **deps-dev:** bump flake8-bugbear from 23.1.20 to 23.2.13 ([#1924](https://github.com/awslabs/aws-lambda-powertools-python/issues/1924)) * **deps-dev:** bump flake8-bugbear from 22.1.11 to 22.4.25 ([#1156](https://github.com/awslabs/aws-lambda-powertools-python/issues/1156)) +* **deps-dev:** bump mypy from 0.942 to 0.950 ([#1162](https://github.com/awslabs/aws-lambda-powertools-python/issues/1162)) +* **deps-dev:** bump flake8-eradicate from 1.2.0 to 1.2.1 ([#1158](https://github.com/awslabs/aws-lambda-powertools-python/issues/1158)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.25.0 to 1.26.0.post1 ([#1691](https://github.com/awslabs/aws-lambda-powertools-python/issues/1691)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.26.63 to 1.26.71 ([#1928](https://github.com/awslabs/aws-lambda-powertools-python/issues/1928)) +* **deps-dev:** bump pytest-benchmark from 3.4.1 to 4.0.0 ([#1659](https://github.com/awslabs/aws-lambda-powertools-python/issues/1659)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.43 to 1.26.77 ([#1949](https://github.com/awslabs/aws-lambda-powertools-python/issues/1949)) +* **deps-dev:** bump types-requests from 2.28.11.2 to 2.28.11.3 ([#1698](https://github.com/awslabs/aws-lambda-powertools-python/issues/1698)) * **deps-dev:** bump xenon from 0.8.0 to 0.9.0 ([#1145](https://github.com/awslabs/aws-lambda-powertools-python/issues/1145)) * **deps-dev:** bump mypy from 0.931 to 0.942 ([#1133](https://github.com/awslabs/aws-lambda-powertools-python/issues/1133)) - -## Regression - -* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) ([#1190](https://github.com/awslabs/aws-lambda-powertools-python/issues/1190)) - - - -## [v1.25.9] - 2022-04-21 -## Bug Fixes - -* **deps:** correct py36 marker for jmespath - -## Maintenance - -* bump to 1.25.9 - - - -## [v1.25.8] - 2022-04-21 -## Bug Fixes - -* removed ambiguous quotes from labels. -* **deps:** update jmespath marker to support 1.0 and py3.6 ([#1139](https://github.com/awslabs/aws-lambda-powertools-python/issues/1139)) -* **governance:** update label in names in issues - -## Documentation - -* **install:** instructions to reduce pydantic package size ([#1077](https://github.com/awslabs/aws-lambda-powertools-python/issues/1077)) -* **layer:** remove link from clipboard button ([#1135](https://github.com/awslabs/aws-lambda-powertools-python/issues/1135)) -* **layer:** update to 1.25.7 - -## Maintenance - -* bump to 1.25.8 -* **deps:** bump codecov/codecov-action from 2.1.0 to 3.0.0 ([#1102](https://github.com/awslabs/aws-lambda-powertools-python/issues/1102)) -* **deps:** bump actions/upload-artifact from 2 to 3 ([#1103](https://github.com/awslabs/aws-lambda-powertools-python/issues/1103)) +* **deps-dev:** bump types-requests from 2.28.11.12 to 2.28.11.13 ([#1933](https://github.com/awslabs/aws-lambda-powertools-python/issues/1933)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.6 to 2.8.19.7 ([#1932](https://github.com/awslabs/aws-lambda-powertools-python/issues/1932)) * **deps-dev:** bump mkdocs-material from 8.2.4 to 8.2.7 ([#1131](https://github.com/awslabs/aws-lambda-powertools-python/issues/1131)) * **deps-dev:** bump pytest from 6.2.5 to 7.0.1 ([#1063](https://github.com/awslabs/aws-lambda-powertools-python/issues/1063)) - - - -## [v1.25.7] - 2022-04-08 -## Bug Fixes - -* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) -* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) -* **idempotency:** pass by value on idem key to guard inadvert mutations ([#1090](https://github.com/awslabs/aws-lambda-powertools-python/issues/1090)) -* **logger:** clear_state should keep custom key formats ([#1095](https://github.com/awslabs/aws-lambda-powertools-python/issues/1095)) -* **middleware_factory:** ret type annotation for handler dec ([#1066](https://github.com/awslabs/aws-lambda-powertools-python/issues/1066)) - -## Documentation - -* **layer:** update to 1.25.6; cosmetic changes - -## Maintenance - -* bump to 1.25.7 -* **governance:** refresh pull request template sections +* **deps-dev:** bump flake8-comprehensions from 3.10.0 to 3.10.1 ([#1699](https://github.com/awslabs/aws-lambda-powertools-python/issues/1699)) +* **deps-dev:** bump mkdocs-material from 8.5.7 to 8.5.9 ([#1697](https://github.com/awslabs/aws-lambda-powertools-python/issues/1697)) +* **deps-dev:** bump aws-cdk-lib from 2.64.0 to 2.65.0 ([#1938](https://github.com/awslabs/aws-lambda-powertools-python/issues/1938)) +* **deps-dev:** bump pytest-xdist from 2.5.0 to 3.0.2 ([#1655](https://github.com/awslabs/aws-lambda-powertools-python/issues/1655)) +* **deps-dev:** bump aws-cdk-lib from 2.65.0 to 2.66.0 ([#1948](https://github.com/awslabs/aws-lambda-powertools-python/issues/1948)) +* **deps-dev:** bump types-requests from 2.28.11.13 to 2.28.11.14 ([#1946](https://github.com/awslabs/aws-lambda-powertools-python/issues/1946)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.7 to 2.8.19.8 ([#1945](https://github.com/awslabs/aws-lambda-powertools-python/issues/1945)) +* **deps-dev:** bump mkdocs-material from 9.0.10 to 9.0.11 ([#1896](https://github.com/awslabs/aws-lambda-powertools-python/issues/1896)) +* **deps-dev:** bump mkdocs-material from 9.0.12 to 9.0.13 ([#1944](https://github.com/awslabs/aws-lambda-powertools-python/issues/1944)) +* **deps-dev:** bump mkdocs-git-revision-date-plugin ([#1146](https://github.com/awslabs/aws-lambda-powertools-python/issues/1146)) +* **docs:** remove v2 banner on top of the docs +* **docs:** bump layer version to 36 (1.29.2) +* **docs:** remove pause sentence from roadmap ([#1409](https://github.com/awslabs/aws-lambda-powertools-python/issues/1409)) +* **docs:** update site name to test ci changelog +* **docs:** update description to trigger changelog generation +* **docs:** update CHANGELOG for v1.26.7 +* **governance:** update rfc to a form * **governance:** update external non-triage effort disclaimer +* **governance:** refresh pull request template sections +* **governance:** update bug report to a form +* **governance:** remove 'area/' from PR labels * **governance:** update static typing to a form -* **governance:** update rfc to a form -* **governance:** update feat request to a form -* **governance:** bug report form typo +* **governance:** warn message on closed issues +* **governance:** warn message on closed issues +* **governance:** auto-merge mypy-stub dependabot +* **governance:** auto-merge to use squash +* **governance:** auto-merge workflow_dispatch off +* **governance:** auto-merge on all PR events +* **governance:** update wording tech debt to summary in maintenance template +* **governance:** add release label on pr merge +* **governance:** fix workflow action requirements & syntax +* **governance:** enforce safe scope on pr merge labelling +* **governance:** limit build workflow to code changes only +* **governance:** check for related issue in new PRs +* **governance:** address gh reusable workflow limitation +* **governance:** add pre-configured dev environment with GitPod.io to ease contributions ([#1403](https://github.com/awslabs/aws-lambda-powertools-python/issues/1403)) +* **governance:** remove markdown rendering from docs issue template +* **governance:** fix typo on semantic commit link introduced in [#1](https://github.com/awslabs/aws-lambda-powertools-python/issues/1)aef4 +* **governance:** fix on_merged_pr workflow syntax +* **governance:** add new maintenance issue template for tech debt ([#1326](https://github.com/awslabs/aws-lambda-powertools-python/issues/1326)) * **governance:** update docs report to a form -* **governance:** update bug report to a form -* **governance:** new ask a question -* **governance:** new static typing report - - - -## [v1.25.6] - 2022-04-01 -## Bug Fixes - -* **logger:** clear_state regression on absent standard keys ([#1088](https://github.com/awslabs/aws-lambda-powertools-python/issues/1088)) - -## Documentation - -* **layer:** bump to 1.25.5 - -## Maintenance - -* bump to 1.25.6 - - - -## [v1.25.5] - 2022-03-18 -## Bug Fixes - -* **logger-utils:** regression on exclude set leading to no formatter ([#1080](https://github.com/awslabs/aws-lambda-powertools-python/issues/1080)) - -## Maintenance - -* bump to 1.25.5 - - - -## [v1.25.4] - 2022-03-17 -## Bug Fixes - -* package_logger as const over logger instance -* repurpose test to cover parent loggers case -* use addHandler over monkeypatch - -## Documentation - -* **appsync:** fix typo -* **contributing:** operational excellence pause -* **layer:** update to 1.25.3 - -## Maintenance - -* bump to 1.25.4 -* remove duplicate test -* comment reason for change -* remove unnecessary test -* lint unused import - -## Regression - -* service_name fixture - -## Pull Requests - -* Merge pull request [#1075](https://github.com/awslabs/aws-lambda-powertools-python/issues/1075) from mploski/fix/existing-loggers-duplicated-logs - - - -## [v1.25.3] - 2022-03-09 -## Bug Fixes - -* **logger:** ensure state is cleared for custom formatters ([#1072](https://github.com/awslabs/aws-lambda-powertools-python/issues/1072)) - -## Documentation - -* **plugin:** add mermaid to create diagram as code ([#1070](https://github.com/awslabs/aws-lambda-powertools-python/issues/1070)) - -## Maintenance - -* bump to 1.25.3 - - - -## [v1.25.2] - 2022-03-07 -## Bug Fixes - -* **event_handler:** docs snippets, high-level import CorsConfig ([#1019](https://github.com/awslabs/aws-lambda-powertools-python/issues/1019)) -* **lambda-authorizer:** allow proxy resources path in arn ([#1051](https://github.com/awslabs/aws-lambda-powertools-python/issues/1051)) -* **metrics:** flush upon a single metric 100th data point ([#1046](https://github.com/awslabs/aws-lambda-powertools-python/issues/1046)) - -## Documentation - -* **layer:** update to 1.25.1 -* **parser:** APIGatewayProxyEvent to APIGatewayProxyEventModel ([#1061](https://github.com/awslabs/aws-lambda-powertools-python/issues/1061)) - -## Maintenance - -* bump to 1.25.2 -* **deps:** bump actions/setup-python from 2.3.1 to 3 ([#1048](https://github.com/awslabs/aws-lambda-powertools-python/issues/1048)) -* **deps:** bump actions/checkout from 2 to 3 ([#1052](https://github.com/awslabs/aws-lambda-powertools-python/issues/1052)) -* **deps:** bump actions/github-script from 5 to 6 ([#1023](https://github.com/awslabs/aws-lambda-powertools-python/issues/1023)) -* **deps:** bump fastjsonschema from 2.15.2 to 2.15.3 ([#949](https://github.com/awslabs/aws-lambda-powertools-python/issues/949)) -* **deps-dev:** bump mkdocs-material from 8.1.9 to 8.2.4 ([#1054](https://github.com/awslabs/aws-lambda-powertools-python/issues/1054)) - - - -## [v1.25.1] - 2022-02-14 -## Bug Fixes - -* **batch:** bugfix to clear exceptions between executions ([#1022](https://github.com/awslabs/aws-lambda-powertools-python/issues/1022)) - -## Maintenance - -* bump to 1.25.1 -* **layers:** bump to 10 for 1.25.0 - - - -## [v1.25.0] - 2022-02-09 -## Bug Fixes - -* **apigateway:** remove indentation in debug_mode ([#987](https://github.com/awslabs/aws-lambda-powertools-python/issues/987)) -* **batch:** delete >10 messages in legacy sqs processor ([#818](https://github.com/awslabs/aws-lambda-powertools-python/issues/818)) -* **ci:** pr label regex for special chars in title -* **logger:** exclude source_logger in copy_config_to_registered_loggers ([#1001](https://github.com/awslabs/aws-lambda-powertools-python/issues/1001)) -* **logger:** test generates logfile - -## Documentation - -* fix syntax errors and line highlights ([#1004](https://github.com/awslabs/aws-lambda-powertools-python/issues/1004)) -* add better BDD coments -* **event-handler:** improve testing section for graphql ([#996](https://github.com/awslabs/aws-lambda-powertools-python/issues/996)) -* **layer:** update to 1.24.2 -* **parameters:** add testing your code section ([#1017](https://github.com/awslabs/aws-lambda-powertools-python/issues/1017)) -* **theme:** upgrade mkdocs-material to 8.x ([#1002](https://github.com/awslabs/aws-lambda-powertools-python/issues/1002)) -* **tutorial:** fix broken internal links ([#1000](https://github.com/awslabs/aws-lambda-powertools-python/issues/1000)) - -## Features - -* **event-handler:** new resolvers to fix current_event typing ([#978](https://github.com/awslabs/aws-lambda-powertools-python/issues/978)) -* **logger:** log_event support event data classes (e.g. S3Event) ([#984](https://github.com/awslabs/aws-lambda-powertools-python/issues/984)) -* **mypy:** complete mypy support for the entire codebase ([#943](https://github.com/awslabs/aws-lambda-powertools-python/issues/943)) - -## Maintenance - -* bump to 1.25.0 -* correct docs -* correct docs -* use isinstance over type -* **deps-dev:** bump flake8-bugbear from 21.11.29 to 22.1.11 ([#955](https://github.com/awslabs/aws-lambda-powertools-python/issues/955)) +* **governance:** update feat request to a form +* **governance:** bug report form typo +* **governance:** new static typing report +* **governance:** remove devcontainer in favour of gitpod.io ([#1411](https://github.com/awslabs/aws-lambda-powertools-python/issues/1411)) +* **governance:** new ask a question +* **governance:** update emeritus affiliation +* **governance:** remove any step relying on master branch +* **layer:** bump to latest version 37 +* **layer:** remove unsused GetFunction permission for the canary +* **layer:** bump to 1.31.1 (v39) +* **layers:** bump to 21 for 1.26.2 +* **layers:** bump to 1.26.5 +* **layers:** bump to 1.26.6 using layer v26 +* **layers:** add dummy v2 layer automation +* **layers:** layer canary stack should not hardcode resource name +* **layers:** replace layers account secret ([#1329](https://github.com/awslabs/aws-lambda-powertools-python/issues/1329)) +* **layers:** expand to all aws commercial regions ([#1324](https://github.com/awslabs/aws-lambda-powertools-python/issues/1324)) +* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) +* **layers:** bump to 22 for 1.26.3 +* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) +* **layers:** bump to 22 for 1.26.3 +* **layers:** upgrade cdk dep hashes to prevent ci fail +* **layers:** bump to 10 for 1.25.0 +* **lint:** use new isort black integration +* **logger:** overload inject_lambda_context with generics ([#1583](https://github.com/awslabs/aws-lambda-powertools-python/issues/1583)) +* **logger:** uncaught exception to use exception value as message +* **maintainer:** add Leandro as maintainer ([#1468](https://github.com/awslabs/aws-lambda-powertools-python/issues/1468)) +* **maintainers:** fix release workflow rename +* **maintainers:** update release workflow link +* **maintainers:** add Ruben as a maintainer ([#1392](https://github.com/awslabs/aws-lambda-powertools-python/issues/1392)) +* **maintenance:** add discord link to first PR and first issue ([#1493](https://github.com/awslabs/aws-lambda-powertools-python/issues/1493)) * **metrics:** fix tests when warnings are disabled ([#994](https://github.com/awslabs/aws-lambda-powertools-python/issues/994)) - -## Pull Requests - -* Merge pull request [#971](https://github.com/awslabs/aws-lambda-powertools-python/issues/971) from gyft/fix-logger-util-tests - - - -## [v1.24.2] - 2022-01-21 -## Bug Fixes - -* **data-classes:** underscore support in api gateway authorizer resource name ([#969](https://github.com/awslabs/aws-lambda-powertools-python/issues/969)) - -## Documentation - -* **layer:** update to 1.24.1 - -## Maintenance - -* bump to 1.24.2 - - - -## [v1.24.1] - 2022-01-20 -## Bug Fixes - -* remove unused json import -* remove apigw contract when using event-handler, apigw tracing -* use decorators, split cold start to ease reading -* incorrect log keys, indentation, snippet consistency -* remove f-strings that doesn't evaluate expr -* **batch:** report multiple failures ([#967](https://github.com/awslabs/aws-lambda-powertools-python/issues/967)) -* **data-classes:** docstring typos and clean up ([#937](https://github.com/awslabs/aws-lambda-powertools-python/issues/937)) -* **parameters:** appconfig internal _get docstrings ([#934](https://github.com/awslabs/aws-lambda-powertools-python/issues/934)) - -## Documentation - -* rename quickstart to tutorial in readme -* rename to tutorial given the size -* add final consideration section -* **batch:** snippet typo on batch processed messages iteration ([#951](https://github.com/awslabs/aws-lambda-powertools-python/issues/951)) -* **batch:** fix typo in context manager keyword ([#938](https://github.com/awslabs/aws-lambda-powertools-python/issues/938)) -* **homepage:** link to typescript version ([#950](https://github.com/awslabs/aws-lambda-powertools-python/issues/950)) -* **install:** new lambda layer for 1.24.0 release -* **metrics:** keep it consistent with other sections, update metric names -* **nav:** make REST and GraphQL event handlers more explicit ([#959](https://github.com/awslabs/aws-lambda-powertools-python/issues/959)) -* **quickstart:** expand on intro line -* **quickstart:** tidy requirements up -* **quickstart:** make section agnostic to json lib -* **quickstart:** same process for Logger -* **quickstart:** add sub-sections, fix highlight & code -* **quickstart:** sentence fragmentation, tidy up -* **tenets:** make core, non-core more explicit -* **tracer:** warning to note on local traces -* **tracer:** add initial image, requirements -* **tracer:** add annotation, metadata, and image -* **tracer:** update ServiceLens image w/ API GW, copywriting -* **tutorial:** fix path to images ([#963](https://github.com/awslabs/aws-lambda-powertools-python/issues/963)) - -## Features - -* **ci:** auto-notify & close issues on release -* **logger:** clone powertools logger config to any Python logger ([#927](https://github.com/awslabs/aws-lambda-powertools-python/issues/927)) - -## Maintenance - -* bump to 1.24.1 -* bump to 1.24.1 -* **ci:** run codeql analysis on push only -* **ci:** fix mergify dependabot queue -* **ci:** add queue name in mergify -* **ci:** remove mergify legacy key -* **ci:** update mergify bot breaking change -* **ci:** safely label PR based on title -* **deps:** bump pydantic from 1.8.2 to 1.9.0 ([#933](https://github.com/awslabs/aws-lambda-powertools-python/issues/933)) -* **deps-dev:** bump mypy from 0.930 to 0.931 ([#941](https://github.com/awslabs/aws-lambda-powertools-python/issues/941)) +* **metrics:** revert dimensions test before splitting ([#1243](https://github.com/awslabs/aws-lambda-powertools-python/issues/1243)) +* **multiple:** localize powertools_dev env logic and warning ([#1570](https://github.com/awslabs/aws-lambda-powertools-python/issues/1570)) +* **package:** correct pyproject version manually +* **parser:** add workaround to make API GW test button work ([#1971](https://github.com/awslabs/aws-lambda-powertools-python/issues/1971)) +* **pypi:** add new links to Pypi package homepage ([#1912](https://github.com/awslabs/aws-lambda-powertools-python/issues/1912)) +* **test-perf:** use pytest-benchmark to improve reliability ([#1250](https://github.com/awslabs/aws-lambda-powertools-python/issues/1250)) +* **tests:** enable end-to-end test workflow ([#1470](https://github.com/awslabs/aws-lambda-powertools-python/issues/1470)) +* **tests:** build and deploy Lambda Layer stack once ([#1466](https://github.com/awslabs/aws-lambda-powertools-python/issues/1466)) +* **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) +* **tests:** refactor E2E logger to ease maintenance, writing tests and parallelization ([#1460](https://github.com/awslabs/aws-lambda-powertools-python/issues/1460)) +* **tests:** move shared_functions to unit tests +* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) ## Regression +* service_name fixture * order to APP logger/service name due to screenshots - -## Pull Requests - -* Merge pull request [#769](https://github.com/awslabs/aws-lambda-powertools-python/issues/769) from mploski/docs/quick-start - - - -## [v1.24.0] - 2021-12-31 -## Bug Fixes - -* **apigateway:** support [@app](https://github.com/app).not_found() syntax & housekeeping ([#926](https://github.com/awslabs/aws-lambda-powertools-python/issues/926)) -* **event-sources:** handle dynamodb null type as none, not bool ([#929](https://github.com/awslabs/aws-lambda-powertools-python/issues/929)) -* **warning:** future distutils deprecation ([#921](https://github.com/awslabs/aws-lambda-powertools-python/issues/921)) - -## Documentation - -* consistency around admonitions and snippets ([#919](https://github.com/awslabs/aws-lambda-powertools-python/issues/919)) -* Added GraphQL Sample API to Examples section of README.md ([#930](https://github.com/awslabs/aws-lambda-powertools-python/issues/930)) -* **batch:** remove leftover from legacy -* **layer:** bump Lambda Layer to version 6 -* **tracer:** new ignore_endpoint feature ([#931](https://github.com/awslabs/aws-lambda-powertools-python/issues/931)) - -## Features - -* **event-sources:** cache parsed json in data class ([#909](https://github.com/awslabs/aws-lambda-powertools-python/issues/909)) -* **feature_flags:** support beyond boolean values (JSON values) ([#804](https://github.com/awslabs/aws-lambda-powertools-python/issues/804)) -* **idempotency:** support dataclasses & pydantic models payloads ([#908](https://github.com/awslabs/aws-lambda-powertools-python/issues/908)) -* **logger:** support use_datetime_directive for timestamps ([#920](https://github.com/awslabs/aws-lambda-powertools-python/issues/920)) -* **tracer:** ignore tracing for certain hostname(s) or url(s) ([#910](https://github.com/awslabs/aws-lambda-powertools-python/issues/910)) - -## Maintenance - -* bump to 1.24.0 -* **deps-dev:** bump mypy from 0.920 to 0.930 ([#925](https://github.com/awslabs/aws-lambda-powertools-python/issues/925)) - - - -## [v1.23.0] - 2021-12-20 -## Bug Fixes - -* **apigateway:** allow list of HTTP methods in route method ([#838](https://github.com/awslabs/aws-lambda-powertools-python/issues/838)) -* **event-sources:** Pass authorizer data to APIGatewayEventAuthorizer ([#897](https://github.com/awslabs/aws-lambda-powertools-python/issues/897)) -* **event-sources:** handle claimsOverrideDetails set to null ([#878](https://github.com/awslabs/aws-lambda-powertools-python/issues/878)) -* **idempotency:** include decorated fn name in hash ([#869](https://github.com/awslabs/aws-lambda-powertools-python/issues/869)) -* **metrics:** explicit type to single_metric ctx manager ([#865](https://github.com/awslabs/aws-lambda-powertools-python/issues/865)) -* **parameters:** appconfig transform and return types ([#877](https://github.com/awslabs/aws-lambda-powertools-python/issues/877)) -* **parser:** overload parse when using envelope ([#885](https://github.com/awslabs/aws-lambda-powertools-python/issues/885)) -* **parser:** kinesis sequence number is str, not int ([#907](https://github.com/awslabs/aws-lambda-powertools-python/issues/907)) -* **parser:** mypy support for payload type override as models ([#883](https://github.com/awslabs/aws-lambda-powertools-python/issues/883)) -* **tracer:** add warm start annotation (ColdStart=False) ([#851](https://github.com/awslabs/aws-lambda-powertools-python/issues/851)) - -## Documentation - -* external reference to cloudformation custom resource helper ([#914](https://github.com/awslabs/aws-lambda-powertools-python/issues/914)) -* add new public Slack invite -* disable search blur in non-prod env -* update Lambda Layers version -* **apigateway:** add new not_found feature ([#915](https://github.com/awslabs/aws-lambda-powertools-python/issues/915)) -* **apigateway:** fix sample layout provided ([#864](https://github.com/awslabs/aws-lambda-powertools-python/issues/864)) -* **appsync:** fix users.py typo to locations [#830](https://github.com/awslabs/aws-lambda-powertools-python/issues/830) -* **lambda_layer:** fix CDK layer syntax - -## Features - -* **apigateway:** add exception_handler support ([#898](https://github.com/awslabs/aws-lambda-powertools-python/issues/898)) -* **apigateway:** access parent api resolver from router ([#842](https://github.com/awslabs/aws-lambda-powertools-python/issues/842)) -* **batch:** new BatchProcessor for SQS, DynamoDB, Kinesis ([#886](https://github.com/awslabs/aws-lambda-powertools-python/issues/886)) -* **logger:** allow handler with custom kwargs signature ([#913](https://github.com/awslabs/aws-lambda-powertools-python/issues/913)) -* **tracer:** add service annotation when service is set ([#861](https://github.com/awslabs/aws-lambda-powertools-python/issues/861)) - -## Maintenance - -* correct pr label order -* minor housekeeping before release ([#912](https://github.com/awslabs/aws-lambda-powertools-python/issues/912)) -* bump to 1.23.0 -* **ci:** split latest docs workflow -* **deps:** bump fastjsonschema from 2.15.1 to 2.15.2 ([#891](https://github.com/awslabs/aws-lambda-powertools-python/issues/891)) -* **deps:** bump actions/setup-python from 2.2.2 to 2.3.0 ([#831](https://github.com/awslabs/aws-lambda-powertools-python/issues/831)) -* **deps:** bump aws-xray-sdk from 2.8.0 to 2.9.0 ([#876](https://github.com/awslabs/aws-lambda-powertools-python/issues/876)) -* **deps:** support arm64 when developing locally ([#862](https://github.com/awslabs/aws-lambda-powertools-python/issues/862)) -* **deps:** bump actions/setup-python from 2.3.0 to 2.3.1 ([#852](https://github.com/awslabs/aws-lambda-powertools-python/issues/852)) -* **deps-dev:** bump flake8 from 3.9.2 to 4.0.1 ([#789](https://github.com/awslabs/aws-lambda-powertools-python/issues/789)) -* **deps-dev:** bump black from 21.10b0 to 21.11b1 ([#839](https://github.com/awslabs/aws-lambda-powertools-python/issues/839)) -* **deps-dev:** bump black from 21.11b1 to 21.12b0 ([#872](https://github.com/awslabs/aws-lambda-powertools-python/issues/872)) -* **deps-dev:** bump mypy from 0.910 to 0.920 ([#903](https://github.com/awslabs/aws-lambda-powertools-python/issues/903)) - - - -## [v1.22.0] - 2021-11-17 -## Bug Fixes - -* change supported python version from 3.6.1 to 3.6.2, bump black ([#807](https://github.com/awslabs/aws-lambda-powertools-python/issues/807)) -* **ci:** comment custom publish version checker -* **ci:** skip sync master on docs hotfix -* **parser:** body/QS can be null or omitted in apigw v1/v2 ([#820](https://github.com/awslabs/aws-lambda-powertools-python/issues/820)) - -## Code Refactoring - -* **apigateway:** Add BaseRouter and duplicate route check ([#757](https://github.com/awslabs/aws-lambda-powertools-python/issues/757)) - -## Documentation - -* updated Lambda Layers definition & limitations. ([#775](https://github.com/awslabs/aws-lambda-powertools-python/issues/775)) -* Idiomatic tenet updated to Progressive -* use higher contrast font ([#822](https://github.com/awslabs/aws-lambda-powertools-python/issues/822)) -* use higher contrast font -* fix indentation of SAM snippets in install section ([#778](https://github.com/awslabs/aws-lambda-powertools-python/issues/778)) -* improve public lambda layer wording, clipboard buttons ([#762](https://github.com/awslabs/aws-lambda-powertools-python/issues/762)) -* add amplify-cli instructions for public layer ([#754](https://github.com/awslabs/aws-lambda-powertools-python/issues/754)) -* **api-gateway:** add support for new router feature ([#767](https://github.com/awslabs/aws-lambda-powertools-python/issues/767)) -* **apigateway:** re-add sample layout, add considerations ([#826](https://github.com/awslabs/aws-lambda-powertools-python/issues/826)) -* **appsync:** add new router feature ([#821](https://github.com/awslabs/aws-lambda-powertools-python/issues/821)) -* **idempotency:** add support for DynamoDB composite keys ([#808](https://github.com/awslabs/aws-lambda-powertools-python/issues/808)) -* **tenets:** update Idiomatic tenet to Progressive ([#823](https://github.com/awslabs/aws-lambda-powertools-python/issues/823)) - -## Features - -* **apigateway:** add Router to allow large routing composition ([#645](https://github.com/awslabs/aws-lambda-powertools-python/issues/645)) -* **appsync:** add Router to allow large resolver composition ([#776](https://github.com/awslabs/aws-lambda-powertools-python/issues/776)) -* **data-classes:** ActiveMQ and RabbitMQ support ([#770](https://github.com/awslabs/aws-lambda-powertools-python/issues/770)) -* **logger:** add ALB correlation ID support ([#816](https://github.com/awslabs/aws-lambda-powertools-python/issues/816)) - -## Maintenance - -* fix var expr -* remove Lambda Layer version tag -* bump to 1.22.0 -* conditional to publish docs only attempt 3 -* conditional to publish docs only attempt 2 -* conditional to publish docs only -* **deps:** bump boto3 from 1.18.58 to 1.18.59 ([#760](https://github.com/awslabs/aws-lambda-powertools-python/issues/760)) -* **deps:** bump boto3 from 1.18.56 to 1.18.58 ([#755](https://github.com/awslabs/aws-lambda-powertools-python/issues/755)) -* **deps:** bump urllib3 from 1.26.4 to 1.26.5 ([#787](https://github.com/awslabs/aws-lambda-powertools-python/issues/787)) -* **deps:** bump boto3 from 1.19.6 to 1.20.3 ([#809](https://github.com/awslabs/aws-lambda-powertools-python/issues/809)) -* **deps:** bump boto3 from 1.18.61 to 1.19.6 ([#783](https://github.com/awslabs/aws-lambda-powertools-python/issues/783)) -* **deps:** bump boto3 from 1.20.3 to 1.20.5 ([#817](https://github.com/awslabs/aws-lambda-powertools-python/issues/817)) -* **deps:** bump boto3 from 1.18.59 to 1.18.61 ([#766](https://github.com/awslabs/aws-lambda-powertools-python/issues/766)) -* **deps-dev:** bump coverage from 6.0.1 to 6.0.2 ([#764](https://github.com/awslabs/aws-lambda-powertools-python/issues/764)) -* **deps-dev:** bump pytest-asyncio from 0.15.1 to 0.16.0 ([#782](https://github.com/awslabs/aws-lambda-powertools-python/issues/782)) -* **deps-dev:** bump flake8-eradicate from 1.1.0 to 1.2.0 ([#784](https://github.com/awslabs/aws-lambda-powertools-python/issues/784)) -* **deps-dev:** bump flake8-isort from 4.0.0 to 4.1.1 ([#785](https://github.com/awslabs/aws-lambda-powertools-python/issues/785)) -* **deps-dev:** bump mkdocs-material from 7.3.2 to 7.3.3 ([#758](https://github.com/awslabs/aws-lambda-powertools-python/issues/758)) -* **deps-dev:** bump flake8-comprehensions from 3.6.1 to 3.7.0 ([#759](https://github.com/awslabs/aws-lambda-powertools-python/issues/759)) -* **deps-dev:** bump mkdocs-material from 7.3.3 to 7.3.5 ([#781](https://github.com/awslabs/aws-lambda-powertools-python/issues/781)) -* **deps-dev:** bump coverage from 6.0 to 6.0.1 ([#751](https://github.com/awslabs/aws-lambda-powertools-python/issues/751)) -* **deps-dev:** bump mkdocs-material from 7.3.5 to 7.3.6 ([#791](https://github.com/awslabs/aws-lambda-powertools-python/issues/791)) -* **deps-dev:** bump coverage from 6.0.2 to 6.1.2 ([#810](https://github.com/awslabs/aws-lambda-powertools-python/issues/810)) -* **deps-dev:** bump isort from 5.9.3 to 5.10.1 ([#811](https://github.com/awslabs/aws-lambda-powertools-python/issues/811)) +* **ci:** new gh-pages beta doesn't work either; reverting as gh-pages is disrupted +* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) ([#1190](https://github.com/awslabs/aws-lambda-powertools-python/issues/1190)) @@ -2950,52 +2345,7 @@ * 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.9.1...HEAD -[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 -[v2.8.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.7.1...v2.8.0 -[v2.7.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.7.0...v2.7.1 -[v2.7.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.6.0...v2.7.0 -[v2.6.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.5.0...v2.6.0 -[v2.5.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.4.0...v2.5.0 -[v2.4.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.3.1...v2.4.0 -[v2.3.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.3.0...v2.3.1 -[v2.3.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.2.0...v2.3.0 -[v2.2.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.1.0...v2.2.0 -[v2.1.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.0.0...v2.1.0 -[v2.0.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.31.1...v2.0.0 -[v1.31.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.31.0...v1.31.1 -[v1.31.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.30.0...v1.31.0 -[v1.30.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.29.2...v1.30.0 -[v1.29.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.29.1...v1.29.2 -[v1.29.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.29.0...v1.29.1 -[v1.29.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.28.0...v1.29.0 -[v1.28.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.27.0...v1.28.0 -[v1.27.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.7...v1.27.0 -[v1.26.7]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.6...v1.26.7 -[v1.26.6]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.5...v1.26.6 -[v1.26.5]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.4...v1.26.5 -[v1.26.4]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.3...v1.26.4 -[v1.26.3]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.2...v1.26.3 -[v1.26.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.1...v1.26.2 -[v1.26.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.0...v1.26.1 -[v1.26.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.10...v1.26.0 -[v1.25.10]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.9...v1.25.10 -[v1.25.9]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.8...v1.25.9 -[v1.25.8]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.7...v1.25.8 -[v1.25.7]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.6...v1.25.7 -[v1.25.6]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.5...v1.25.6 -[v1.25.5]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.4...v1.25.5 -[v1.25.4]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.3...v1.25.4 -[v1.25.3]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.2...v1.25.3 -[v1.25.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.1...v1.25.2 -[v1.25.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.0...v1.25.1 -[v1.25.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.24.2...v1.25.0 -[v1.24.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.24.1...v1.24.2 -[v1.24.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.24.0...v1.24.1 -[v1.24.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.23.0...v1.24.0 -[v1.23.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.22.0...v1.23.0 -[v1.22.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.21.1...v1.22.0 +[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.21.1...HEAD [v1.21.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.21.0...v1.21.1 [v1.21.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.20.2...v1.21.0 [v1.20.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.20.1...v1.20.2 From da1e745d0801518f1c515c28409c3f603436cf2f Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 3 Mar 2023 13:12:16 +0100 Subject: [PATCH 02/75] Add batch processing to appsync handler --- .../event_handler/appsync.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 316792e4119..cdb127068d9 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,8 +1,10 @@ import logging -from typing import Any, Callable, Optional, Type, TypeVar +from typing import Any, Callable, Optional, Type, TypeVar, List, Union from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext +from itertools import groupby +from operator import itemgetter logger = logging.getLogger(__name__) @@ -10,7 +12,7 @@ class BaseRouter: - current_event: AppSyncResolverEventT # type: ignore[valid-type] + current_event: Union[AppSyncResolverEventT, List[AppSyncResolverEventT]] # type: ignore[valid-type] lambda_context: LambdaContext context: dict @@ -152,11 +154,26 @@ def lambda_handler(event, context): If we could not find a field resolver """ # Maintenance: revisit generics/overload to fix [attr-defined] in mypy usage - BaseRouter.current_event = data_model(event) + + # If event is a list it means that AppSync sent batch request + if isinstance(event, list): + event_groups = [ + {"field_name": field_name, "events": list(events)} + for field_name, events in groupby(event, key=lambda x: x["info"]["fieldName"]) + ] + if len(event_groups) > 1: + ValueError("batch with different field names. It shouldn't happen!") + + BaseRouter.current_event = [data_model(event) for event in event_groups[0]["events"]] + + resolver = self._get_resolver(BaseRouter.current_event[0].type_name, event_groups[0]["field_name"]) + response = resolver() + else: + BaseRouter.current_event = data_model(event) + resolver = self._get_resolver(BaseRouter.current_event.type_name, BaseRouter.current_event.field_name) + response = resolver(**BaseRouter.current_event.arguments) BaseRouter.lambda_context = context - resolver = self._get_resolver(BaseRouter.current_event.type_name, BaseRouter.current_event.field_name) - response = resolver(**BaseRouter.current_event.arguments) self.clear_context() return response From 9c5cbd9ecbf3c512ee1a345f0ffefa00b09308ee Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 3 Mar 2023 15:49:55 +0100 Subject: [PATCH 03/75] Extend router to accept List of events. Add functional test --- .../event_handler/appsync.py | 21 ++-- .../event_handler/handlers/appsync_handler.py | 100 ++++++++++++++++++ .../functional/event_handler/test_appsync.py | 53 ++++++++++ 3 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 tests/e2e/event_handler/handlers/appsync_handler.py diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index cdb127068d9..4132cc3524d 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,10 +1,9 @@ import logging -from typing import Any, Callable, Optional, Type, TypeVar, List, Union +from itertools import groupby +from typing import Any, Callable, List, Optional, Type, TypeVar, Union from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext -from itertools import groupby -from operator import itemgetter logger = logging.getLogger(__name__) @@ -155,6 +154,8 @@ def lambda_handler(event, context): """ # Maintenance: revisit generics/overload to fix [attr-defined] in mypy usage + BaseRouter.lambda_context = context + # If event is a list it means that AppSync sent batch request if isinstance(event, list): event_groups = [ @@ -164,15 +165,15 @@ def lambda_handler(event, context): if len(event_groups) > 1: ValueError("batch with different field names. It shouldn't happen!") - BaseRouter.current_event = [data_model(event) for event in event_groups[0]["events"]] - - resolver = self._get_resolver(BaseRouter.current_event[0].type_name, event_groups[0]["field_name"]) + appconfig_events = [data_model(event) for event in event_groups[0]["events"]] + BaseRouter.current_event = appconfig_events + resolver = self._get_resolver(appconfig_events[0].type_name, event_groups[0]["field_name"]) response = resolver() else: - BaseRouter.current_event = data_model(event) - resolver = self._get_resolver(BaseRouter.current_event.type_name, BaseRouter.current_event.field_name) - response = resolver(**BaseRouter.current_event.arguments) - BaseRouter.lambda_context = context + appconfig_event = data_model(event) + BaseRouter.current_event = appconfig_event + resolver = self._get_resolver(appconfig_event.type_name, appconfig_event.field_name) + response = resolver(**appconfig_event.arguments) self.clear_context() diff --git a/tests/e2e/event_handler/handlers/appsync_handler.py b/tests/e2e/event_handler/handlers/appsync_handler.py new file mode 100644 index 00000000000..5c2147bf932 --- /dev/null +++ b/tests/e2e/event_handler/handlers/appsync_handler.py @@ -0,0 +1,100 @@ +from typing import List + + +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.typing import LambdaContext +from pydantic import BaseModel + +app = AppSyncResolver() + + +posts = { + "1": { + "id": "1", + "title": "First book", + "author": "Author1", + "url": "https://amazon.com/", + "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", + "ups": "100", + "downs": "10", + }, + "2": { + "id": "2", + "title": "Second book", + "author": "Author2", + "url": "https://amazon.com", + "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", + "ups": "100", + "downs": "10", + }, + "3": { + "id": "3", + "title": "Third book", + "author": "Author3", + "url": None, + "content": None, + "ups": None, + "downs": None, + }, + "4": { + "id": "4", + "title": "Fourth book", + "author": "Author4", + "url": "https://www.amazon.com/", + "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", + "ups": "1000", + "downs": "0", + }, + "5": { + "id": "5", + "title": "Fifth book", + "author": "Author5", + "url": "https://www.amazon.com/", + "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", + "ups": "50", + "downs": "0", + }, +} + +posts_related = { + "1": [posts["4"]], + "2": [posts["3"], posts["5"]], + "3": [posts["2"], posts["1"]], + "4": [posts["2"], posts["1"]], + "5": [], +} + + +class Post(BaseModel): + id: str + author: str + title: str + url: str + content: str + ups: str + downs: str + + +@app.resolver(type_name="Query", field_name="getPost") +def get_post(id: str = "") -> dict: + post = Post(**posts[id]).dict() + return post + + +@app.resolver(type_name="Query", field_name="allPosts") +def all_posts() -> List[dict]: + parsed_posts = [post for post in posts.values()] + return parsed_posts + + +@app.resolver(type_name="Post", field_name="relatedPosts") +def related_posts() -> List[dict]: + posts = [] + for resolver_event in app.current_event: + if resolver_event.source: + posts.append(posts_related[resolver_event.source["id"]]) + return posts + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 54695eba240..9cb833a161d 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -164,6 +164,59 @@ def create_something(id: str): # noqa AA03 VNE003 assert app.current_event.country_viewer == "US" +def test_resolve_batch_processing(): + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "3", + }, + }, + ] + + app = AppSyncResolver() + + @app.resolver(field_name="listLocations") + def create_something(): # noqa AA03 VNE003 + return [event.source["id"] for event in app.current_event] + + # Call the implicit handler + result = app.resolve(event, LambdaContext()) + assert result == ["1", "2", "3"] + + assert len(app.current_event) == len(event) + + def test_resolver_include_resolver(): # GIVEN app = AppSyncResolver() From 4793efdec4df25016bf455a8b1a28bfb9e7746d5 Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 10 Mar 2023 18:15:43 +0100 Subject: [PATCH 04/75] Add e2e tests --- tests/e2e/event_handler/files/schema.graphql | 19 +++ ...handler.py => appsync_resolver_handler.py} | 0 tests/e2e/event_handler/infrastructure.py | 51 ++++++++- .../event_handler/test_appsync_resolvers.py | 108 ++++++++++++++++++ 4 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 tests/e2e/event_handler/files/schema.graphql rename tests/e2e/event_handler/handlers/{appsync_handler.py => appsync_resolver_handler.py} (100%) create mode 100644 tests/e2e/event_handler/test_appsync_resolvers.py diff --git a/tests/e2e/event_handler/files/schema.graphql b/tests/e2e/event_handler/files/schema.graphql new file mode 100644 index 00000000000..b00e185ccfb --- /dev/null +++ b/tests/e2e/event_handler/files/schema.graphql @@ -0,0 +1,19 @@ +schema { + query: Query +} + +type Query { + getPost(id:ID!): Post + allPosts: [Post] +} + +type Post { + id: ID! + author: String! + title: String + content: String + url: String + ups: Int + downs: Int + relatedPosts: [Post] +} \ No newline at end of file diff --git a/tests/e2e/event_handler/handlers/appsync_handler.py b/tests/e2e/event_handler/handlers/appsync_resolver_handler.py similarity index 100% rename from tests/e2e/event_handler/handlers/appsync_handler.py rename to tests/e2e/event_handler/handlers/appsync_resolver_handler.py diff --git a/tests/e2e/event_handler/infrastructure.py b/tests/e2e/event_handler/infrastructure.py index ca0d1ad8378..7f2c552cf10 100644 --- a/tests/e2e/event_handler/infrastructure.py +++ b/tests/e2e/event_handler/infrastructure.py @@ -1,6 +1,7 @@ +from pathlib import Path from typing import Dict, Optional -from aws_cdk import CfnOutput +from aws_cdk import CfnOutput, Duration, Expiration from aws_cdk import aws_apigateway as apigwv1 from aws_cdk import aws_apigatewayv2_alpha as apigwv2 from aws_cdk import aws_apigatewayv2_authorizers_alpha as apigwv2authorizers @@ -8,6 +9,8 @@ from aws_cdk import aws_ec2 as ec2 from aws_cdk import aws_elasticloadbalancingv2 as elbv2 from aws_cdk import aws_elasticloadbalancingv2_targets as targets +from aws_cdk import aws_appsync_alpha as appsync +from aws_cdk import aws_iam from aws_cdk.aws_lambda import Function, FunctionUrlAuthType from tests.e2e.utils.infrastructure import BaseInfrastructure @@ -17,10 +20,11 @@ class EventHandlerStack(BaseInfrastructure): def create_resources(self): functions = self.create_lambda_functions() - self._create_alb(function=functions["AlbHandler"]) - self._create_api_gateway_rest(function=functions["ApiGatewayRestHandler"]) - self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"]) - self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"]) + # self._create_alb(function=functions["AlbHandler"]) + # self._create_api_gateway_rest(function=functions["ApiGatewayRestHandler"]) + # self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"]) + # self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"]) + self._create_appsync_endpoint(function=functions["AppsyncResolverHandler"]) def _create_alb(self, function: Function): vpc = ec2.Vpc.from_lookup( @@ -84,3 +88,40 @@ def _create_lambda_function_url(self, function: Function): # Maintenance: move auth to IAM when we create sigv4 builders function_url = function.add_function_url(auth_type=FunctionUrlAuthType.AWS_IAM) CfnOutput(self.stack, "LambdaFunctionUrl", value=function_url.url) + + def _create_appsync_endpoint(self, function: Function): + api = appsync.GraphqlApi( + self.stack, + "Api", + name="e2e-tests", + schema=appsync.SchemaFile.from_asset(str(Path(self.feature_path, "files/schema.graphql"))), + authorization_config=appsync.AuthorizationConfig( + default_authorization=appsync.AuthorizationMode( + authorization_type=appsync.AuthorizationType.API_KEY, + api_key_config=appsync.ApiKeyConfig( + description="public key for getting data", + expires=Expiration.after(Duration.hours(25)), + name="API Token", + ), + ) + ), + xray_enabled=False, + ) + lambda_datasource = api.add_lambda_data_source("DataSource", lambda_function=function) + + lambda_datasource.create_resolver( + "QueryGetAllPostsResolver", + type_name="Query", + field_name="allPosts", + ) + lambda_datasource.create_resolver( + "QueryGetPostResolver", + type_name="Query", + field_name="getPost", + ) + lambda_datasource.create_resolver( + "QueryGetPostRelatedResolver", type_name="Post", field_name="relatedPosts", max_batch_size=10 + ) + + CfnOutput(self.stack, "GraphQLHTTPUrl", value=api.graphql_url) + CfnOutput(self.stack, "GraphQLAPIKey", value=api.api_key) diff --git a/tests/e2e/event_handler/test_appsync_resolvers.py b/tests/e2e/event_handler/test_appsync_resolvers.py new file mode 100644 index 00000000000..bc3c89d2be2 --- /dev/null +++ b/tests/e2e/event_handler/test_appsync_resolvers.py @@ -0,0 +1,108 @@ +import json +import pytest +from requests import HTTPError, Request + +from tests.e2e.utils import data_fetcher +from tests.e2e.utils.auth import build_iam_auth + + +@pytest.fixture +def appsync_endpoint(infrastructure: dict) -> str: + return infrastructure["GraphQLHTTPUrl"] + + +@pytest.fixture +def appsync_access_key(infrastructure: dict) -> str: + return infrastructure["GraphQLAPIKey"] + + +@pytest.mark.xdist_group(name="event_handler") +def test_appsync_get_all_posts(appsync_endpoint, appsync_access_key): + # GIVEN + body = { + "query": "query MyQuery { allPosts { id }}", + "variables": None, + "operationName": "MyQuery", + } + + # WHEN + response = data_fetcher.get_http_response( + Request( + method="POST", + url=appsync_endpoint, + json=body, + headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, + ) + ) + + # THEN expect a HTTP 200 response and content return list of Posts + assert response.status_code == 200 + assert response.content is not None + + data = json.loads(response.content.decode("ascii"))["data"] + + assert data["allPosts"] is not None + assert len(data["allPosts"]) > 0 + + +@pytest.mark.xdist_group(name="event_handler") +def test_appsync_get_post(appsync_endpoint, appsync_access_key): + # GIVEN + post_id = "1" + body = { + "query": f'query MyQuery {{ getPost(id: "{post_id}") {{ id }} }}', + "variables": None, + "operationName": "MyQuery", + } + + # WHEN + response = data_fetcher.get_http_response( + Request( + method="POST", + url=appsync_endpoint, + json=body, + headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, + ) + ) + + # THEN expect a HTTP 200 response and content return Post id + assert response.status_code == 200 + assert response.content is not None + + data = json.loads(response.content.decode("ascii"))["data"] + + assert data["getPost"]["id"] == post_id + + +@pytest.mark.xdist_group(name="event_handler") +def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): + # GIVEN + post_id = "2" + related_posts_ids = ["3", "5"] + + body = { + "query": f'query MyQuery {{ getPost(id: "{post_id}") {{ id relatedPosts {{ id }} }} }}', + "variables": None, + "operationName": "MyQuery", + } + + # WHEN + response = data_fetcher.get_http_response( + Request( + method="POST", + url=appsync_endpoint, + json=body, + headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, + ) + ) + + # THEN expect a HTTP 200 response and content return Post id with dependent Posts id's + assert response.status_code == 200 + assert response.content is not None + + data = json.loads(response.content.decode("ascii"))["data"] + + assert data["getPost"]["id"] == post_id + assert len(data["getPost"]["relatedPosts"]) == len(related_posts_ids) + for post in data["getPost"]["relatedPosts"]: + assert post["id"] in related_posts_ids From e3ca8c5b1729f1c9414ed14d6914e66fac48f213 Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 10 Mar 2023 18:18:30 +0100 Subject: [PATCH 05/75] Add required package --- poetry.lock | 19 +++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 20 insertions(+) diff --git a/poetry.lock b/poetry.lock index 4dcf14cd454..01b8d40993a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -151,6 +151,25 @@ jsii = ">=1.74.0,<2.0.0" publication = ">=0.0.3" typeguard = ">=2.13.3,<2.14.0" +[[package]] +name = "aws-cdk-aws-appsync-alpha" +version = "2.59.0a0" +description = "The CDK Construct Library for AWS::AppSync" +category = "dev" +optional = false +python-versions = "~=3.7" +files = [ + {file = "aws-cdk.aws-appsync-alpha-2.59.0a0.tar.gz", hash = "sha256:f5c7773b70b759efd576561dc3d71af5762a6f7cbc9ee9eef5e538c7ab3dccc7"}, + {file = "aws_cdk.aws_appsync_alpha-2.59.0a0-py3-none-any.whl", hash = "sha256:ecc235f1f70d404c8d03cf250be0227becd14c468f8c43b6d9df334a1d60c8e2"}, +] + +[package.dependencies] +aws-cdk-lib = ">=2.59.0,<3.0.0" +constructs = ">=10.0.0,<11.0.0" +jsii = ">=1.72.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + [[package]] name = "aws-cdk-lib" version = "2.68.0" diff --git a/pyproject.toml b/pyproject.toml index ce91e46484b..bca0beefd90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,7 @@ cfn-lint = "0.74.1" mypy = "^0.982" types-python-dateutil = "^2.8.19.6" httpx = "^0.23.3" +aws-cdk-aws-appsync-alpha = "^2.59.0a0" [tool.coverage.run] source = ["aws_lambda_powertools"] From d0fe8673b5fd6b4a4ecec72c72e3bd853407677f Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 10 Mar 2023 22:18:52 +0100 Subject: [PATCH 06/75] Fix linter checks --- CHANGELOG.md | 2470 +++++++++++------ tests/e2e/event_handler/files/schema.graphql | 4 +- .../handlers/appsync_resolver_handler.py | 31 +- tests/e2e/event_handler/infrastructure.py | 11 +- .../event_handler/test_appsync_resolvers.py | 16 +- 5 files changed, 1590 insertions(+), 942 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5592a47d66..6d3e7baf511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,985 +6,1590 @@ ## Bug Fixes -* typo in input for layer workflow -* change supported python version from 3.6.1 to 3.6.2, bump black ([#807](https://github.com/awslabs/aws-lambda-powertools-python/issues/807)) -* add entire ARN role instead of account and role name -* path to artefact -* lint files -* package_logger as const over logger instance -* remove f-strings that doesn't evaluate expr -* incorrect log keys, indentation, snippet consistency -* unzip the right artifact name -* use decorators, split cold start to ease reading -* remove apigw contract when using event-handler, apigw tracing -* remove unused json import -* removed ambiguous quotes from labels. -* download artefact into the layer dir -* parallel_run should fail when e2e tests fail -* bump aws-cdk version -* git-chlg docker image is broken -* repurpose test to cover parent loggers case -* mathc the name of the cdk synth from the build phase -* sight, yes a whitespace character breaks the build -* no need to cache npm since we only install cdk cli and don't have .lock files -* use addHandler over monkeypatch -* lock dependencies -* mypy errors -* **api_gateway:** fixed custom metrics issue when using debug mode ([#1827](https://github.com/awslabs/aws-lambda-powertools-python/issues/1827)) -* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) -* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) -* **apigateway:** remove indentation in debug_mode ([#987](https://github.com/awslabs/aws-lambda-powertools-python/issues/987)) -* **apigateway:** support nested router decorators ([#1709](https://github.com/awslabs/aws-lambda-powertools-python/issues/1709)) -* **apigateway:** support [@app](https://github.com/app).not_found() syntax & housekeeping ([#926](https://github.com/awslabs/aws-lambda-powertools-python/issues/926)) -* **apigateway:** allow list of HTTP methods in route method ([#838](https://github.com/awslabs/aws-lambda-powertools-python/issues/838)) -* **apigateway:** support dynamic routes with equal sign (RFC3986) ([#1737](https://github.com/awslabs/aws-lambda-powertools-python/issues/1737)) -* **apigateway:** update Response class to require status_code only ([#1560](https://github.com/awslabs/aws-lambda-powertools-python/issues/1560)) -* **batch:** report multiple failures ([#967](https://github.com/awslabs/aws-lambda-powertools-python/issues/967)) -* **batch:** docstring fix for success_handler() record parameter ([#1202](https://github.com/awslabs/aws-lambda-powertools-python/issues/1202)) -* **batch:** bugfix to clear exceptions between executions ([#1022](https://github.com/awslabs/aws-lambda-powertools-python/issues/1022)) -* **batch:** delete >10 messages in legacy sqs processor ([#818](https://github.com/awslabs/aws-lambda-powertools-python/issues/818)) -* **batch:** missing space in BatchProcessingError message ([#1201](https://github.com/awslabs/aws-lambda-powertools-python/issues/1201)) -* **ci:** ensure PR_AUTHOR is present for large_pr_split workflow -* **ci:** secret and OIDC inheritance in nested children workflow -* **ci:** temporarly remove pypi test deployment -* **ci:** new artifact path, sed gnu/linux syntax, and pypi test -* **ci:** workflow should use npx for CDK CLI -* **ci:** remove v2 suffix from SAR apps ([#1633](https://github.com/awslabs/aws-lambda-powertools-python/issues/1633)) -* **ci:** fix arm64 layer builds -* **ci:** integrate isort 5.0 with black to resolve conflicts * **ci:** bump CDK version -* **ci:** build without buildkit -* **ci:** use docker driver on buildx -* **ci:** linting issues after flake8-blackbear,mypy upgrades -* **ci:** setup git client earlier to prevent dirty stash error -* **ci:** ignore v2 action for now -* **ci:** only run e2e tests on py 3.7 -* **ci:** reusable workflow secrets param -* **ci:** pass core fns to large pr workflow script -* **ci:** on_label permissioning model & workflow execution -* **ci:** increase permission to allow version sync back to repo -* **ci:** gracefully and successful exit changelog upon no changes -* **ci:** event resolution for on_label_added workflow -* **ci:** calculate parallel jobs based on infrastructure needs ([#1475](https://github.com/awslabs/aws-lambda-powertools-python/issues/1475)) -* **ci:** del flake8 direct dep over py3.6 conflicts and docs failure -* **ci:** remove utf-8 body in octokit body req -* **ci:** move from pip-tools to poetry on layers to fix conflicts -* **ci:** typo and bust gh actions cache -* **ci:** use poetry to resolve layer deps; pip for CDK -* **ci:** disable poetry venv for layer workflow as cdk ignores venv -* **ci:** add cdk v2 dep for layers workflow -* **ci:** move from pip-tools to poetry on layers reusable workflow -* **ci:** move from pip-tools to poetry on layers -* **ci:** temporarily disable changelog upon release -* **ci:** add explicit origin to fix release detached head -* **ci:** quote prBody GH expr on_opened_pr -* **ci:** changelog workflow must receive git tags too -* **ci:** add additional input to accurately describe intent on skip -* **ci:** job permissions -* **ci:** merged_pr add issues write access -* **ci:** add missing oidc token generation permission -* **ci:** disable merged_pr workflow -* **ci:** allow inherit secrets for reusable workflow -* **ci:** remove unused secret -* **ci:** label_related_issue unresolved var from history mixup -* **ci:** cond doesnt support two expr w/ env -* **ci:** only event is resolved in cond -* **ci:** remove unsupported env in workflow_call -* **ci:** unexpected symbol due to double quotes... -* **ci:** improve msg visibility on closed issues -* **ci:** keep layer version permission ([#1318](https://github.com/awslabs/aws-lambda-powertools-python/issues/1318)) -* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) -* **ci:** fetch all git info so we can check tags -* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) -* **ci:** remove additional quotes in PR action ([#1317](https://github.com/awslabs/aws-lambda-powertools-python/issues/1317)) -* **ci:** install poetry before calling setup/python with cache ([#1315](https://github.com/awslabs/aws-lambda-powertools-python/issues/1315)) -* **ci:** checkout project before validating related issue workflow -* **ci:** fixes typos and small issues on github scripts ([#1302](https://github.com/awslabs/aws-lambda-powertools-python/issues/1302)) -* **ci:** address conditional type on_merge -* **ci:** address pr title semantic not found logic -* **ci:** address gh-actions additional quotes; remove debug -* **ci:** regex group name for on_merge workflow -* **ci:** api docs path -* **ci:** move conditionals from yaml to code; leftover -* **ci:** move conditionals from yaml to code -* **ci:** accept core arg in label related issue workflow -* **ci:** match the name of the cdk synth from the build phase -* **ci:** regex to catch combination of related issues workflow -* **ci:** checkout project before validating related issue workflow -* **ci:** regex to catch combination of related issues workflow -* **ci:** use gh-pages env as official docs are wrong -* **ci:** pr label regex for special chars in title -* **ci:** scope e2e tests by python version -* **ci:** add auth to API HTTP Gateway and Lambda Function Url ([#1882](https://github.com/awslabs/aws-lambda-powertools-python/issues/1882)) -* **ci:** comment custom publish version checker + +## Documentation + +* **homepage:** revamp install UX & share how we build Lambda Layer ([#1978](https://github.com/awslabs/aws-lambda-powertools-python/issues/1978)) + +## Maintenance + +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.1 to 2.1.2 ([#1979](https://github.com/awslabs/aws-lambda-powertools-python/issues/1979)) +* **deps-dev:** bump pytest from 7.2.1 to 7.2.2 ([#1980](https://github.com/awslabs/aws-lambda-powertools-python/issues/1980)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.9 to 2.8.19.10 ([#1973](https://github.com/awslabs/aws-lambda-powertools-python/issues/1973)) +* **deps-dev:** bump hvac from 1.0.2 to 1.1.0 ([#1983](https://github.com/awslabs/aws-lambda-powertools-python/issues/1983)) +* **deps-dev:** bump mkdocs-material from 9.1.0 to 9.1.1 ([#1984](https://github.com/awslabs/aws-lambda-powertools-python/issues/1984)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.24 to 1.26.84 ([#1981](https://github.com/awslabs/aws-lambda-powertools-python/issues/1981)) +* **deps-dev:** bump mkdocs-material from 9.0.15 to 9.1.0 ([#1976](https://github.com/awslabs/aws-lambda-powertools-python/issues/1976)) +* **deps-dev:** bump cfn-lint from 0.67.0 to 0.74.0 ([#1974](https://github.com/awslabs/aws-lambda-powertools-python/issues/1974)) +* **deps-dev:** bump aws-cdk-lib from 2.66.1 to 2.67.0 ([#1977](https://github.com/awslabs/aws-lambda-powertools-python/issues/1977)) + + + +## [v2.9.1] - 2023-03-01 +## Bug Fixes + +* **idempotency:** revert dict mutation that impacted static_pk_value feature ([#1970](https://github.com/awslabs/aws-lambda-powertools-python/issues/1970)) + +## Documentation + +* **appsync:** add mutation example and infrastructure fix ([#1964](https://github.com/awslabs/aws-lambda-powertools-python/issues/1964)) +* **parameters:** fix typos and inconsistencies ([#1966](https://github.com/awslabs/aws-lambda-powertools-python/issues/1966)) + +## Maintenance + +* update project description +* update v2 layer ARN on documentation +* **ci:** disable pypi test due to maintenance mode +* **ci:** replace deprecated set-output commands ([#1957](https://github.com/awslabs/aws-lambda-powertools-python/issues/1957)) +* **deps:** bump fastjsonschema from 2.16.2 to 2.16.3 ([#1961](https://github.com/awslabs/aws-lambda-powertools-python/issues/1961)) +* **deps:** bump release-drafter/release-drafter from 5.22.0 to 5.23.0 ([#1947](https://github.com/awslabs/aws-lambda-powertools-python/issues/1947)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.0 to 2.1.1 ([#1958](https://github.com/awslabs/aws-lambda-powertools-python/issues/1958)) +* **deps-dev:** bump coverage from 7.2.0 to 7.2.1 ([#1963](https://github.com/awslabs/aws-lambda-powertools-python/issues/1963)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.8 to 2.8.19.9 ([#1960](https://github.com/awslabs/aws-lambda-powertools-python/issues/1960)) +* **deps-dev:** bump mkdocs-material from 9.0.14 to 9.0.15 ([#1959](https://github.com/awslabs/aws-lambda-powertools-python/issues/1959)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.55 to 1.26.80 ([#1967](https://github.com/awslabs/aws-lambda-powertools-python/issues/1967)) +* **deps-dev:** bump types-requests from 2.28.11.14 to 2.28.11.15 ([#1962](https://github.com/awslabs/aws-lambda-powertools-python/issues/1962)) +* **deps-dev:** bump aws-cdk-lib from 2.66.0 to 2.66.1 ([#1954](https://github.com/awslabs/aws-lambda-powertools-python/issues/1954)) +* **deps-dev:** bump coverage from 7.1.0 to 7.2.0 ([#1951](https://github.com/awslabs/aws-lambda-powertools-python/issues/1951)) +* **deps-dev:** bump mkdocs-material from 9.0.13 to 9.0.14 ([#1952](https://github.com/awslabs/aws-lambda-powertools-python/issues/1952)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.43 to 1.26.77 ([#1949](https://github.com/awslabs/aws-lambda-powertools-python/issues/1949)) +* **deps-dev:** bump types-requests from 2.28.11.13 to 2.28.11.14 ([#1946](https://github.com/awslabs/aws-lambda-powertools-python/issues/1946)) +* **deps-dev:** bump aws-cdk-lib from 2.65.0 to 2.66.0 ([#1948](https://github.com/awslabs/aws-lambda-powertools-python/issues/1948)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.7 to 2.8.19.8 ([#1945](https://github.com/awslabs/aws-lambda-powertools-python/issues/1945)) +* **parser:** add workaround to make API GW test button work ([#1971](https://github.com/awslabs/aws-lambda-powertools-python/issues/1971)) + + + +## [v2.9.0] - 2023-02-21 +## Bug Fixes + * **ci:** upgraded cdk to match the version used on e2e tests -* **ci:** escape outputs as certain PRs can break GH Actions expressions -* **ci:** disable pre-commit hook download from version bump -* **ci:** skip sync master on docs hotfix -* **core:** fixes leftovers from rebase -* **data-classes:** underscore support in api gateway authorizer resource name ([#969](https://github.com/awslabs/aws-lambda-powertools-python/issues/969)) -* **data-classes:** Add missing SES fields and ([#1045](https://github.com/awslabs/aws-lambda-powertools-python/issues/1045)) -* **data-classes:** docstring typos and clean up ([#937](https://github.com/awslabs/aws-lambda-powertools-python/issues/937)) -* **deps:** bump dev dep mako version to address CVE-2022-40023 ([#1524](https://github.com/awslabs/aws-lambda-powertools-python/issues/1524)) -* **deps:** correct py36 marker for jmespath -* **deps:** Ignore boto3 changes until needed ([#1151](https://github.com/awslabs/aws-lambda-powertools-python/issues/1151)) -* **deps:** update jmespath marker to support 1.0 and py3.6 ([#1139](https://github.com/awslabs/aws-lambda-powertools-python/issues/1139)) -* **deps:** correct mypy types as dev dependency ([#1322](https://github.com/awslabs/aws-lambda-powertools-python/issues/1322)) -* **deps:** update build system to poetry-core ([#1651](https://github.com/awslabs/aws-lambda-powertools-python/issues/1651)) -* **deps-dev:** remove jmespath due to dev deps conflict ([#1148](https://github.com/awslabs/aws-lambda-powertools-python/issues/1148)) -* **docs:** remove Slack link ([#1210](https://github.com/awslabs/aws-lambda-powertools-python/issues/1210)) -* **event-handler:** body to empty string in CORS preflight (ALB non-compliant) ([#1249](https://github.com/awslabs/aws-lambda-powertools-python/issues/1249)) -* **event-sources:** Pass authorizer data to APIGatewayEventAuthorizer ([#897](https://github.com/awslabs/aws-lambda-powertools-python/issues/897)) -* **event-sources:** handle dynamodb null type as none, not bool ([#929](https://github.com/awslabs/aws-lambda-powertools-python/issues/929)) -* **event-sources:** handle claimsOverrideDetails set to null ([#878](https://github.com/awslabs/aws-lambda-powertools-python/issues/878)) -* **event_handler:** exception_handler to handle ServiceError exceptions ([#1160](https://github.com/awslabs/aws-lambda-powertools-python/issues/1160)) -* **event_handler:** Allow for event_source support ([#1159](https://github.com/awslabs/aws-lambda-powertools-python/issues/1159)) -* **event_handler:** docs snippets, high-level import CorsConfig ([#1019](https://github.com/awslabs/aws-lambda-powertools-python/issues/1019)) -* **event_handlers:** ImportError when importing Response from top-level event_handler ([#1388](https://github.com/awslabs/aws-lambda-powertools-python/issues/1388)) -* **event_handlers:** omit explicit None HTTP header values ([#1793](https://github.com/awslabs/aws-lambda-powertools-python/issues/1793)) -* **event_handlers:** handle lack of headers when using auto-compression feature ([#1325](https://github.com/awslabs/aws-lambda-powertools-python/issues/1325)) -* **event_sources:** add test for Function URL AuthZ ([#1421](https://github.com/awslabs/aws-lambda-powertools-python/issues/1421)) -* **event_sources:** implement Mapping protocol on DictWrapper for better interop with existing middlewares ([#1516](https://github.com/awslabs/aws-lambda-powertools-python/issues/1516)) * **feature-flags:** revert RuleAction Enum inheritance on str ([#1910](https://github.com/awslabs/aws-lambda-powertools-python/issues/1910)) -* **governance:** update label in names in issues -* **idempotency:** make idempotent_function decorator thread safe ([#1899](https://github.com/awslabs/aws-lambda-powertools-python/issues/1899)) -* **idempotency:** revert dict mutation that impacted static_pk_value feature ([#1970](https://github.com/awslabs/aws-lambda-powertools-python/issues/1970)) -* **idempotency:** idempotent_function should support standalone falsy values ([#1669](https://github.com/awslabs/aws-lambda-powertools-python/issues/1669)) -* **idempotency:** pass by value on idem key to guard inadvert mutations ([#1090](https://github.com/awslabs/aws-lambda-powertools-python/issues/1090)) -* **idempotency:** include decorated fn name in hash ([#869](https://github.com/awslabs/aws-lambda-powertools-python/issues/869)) -* **jmespath_util:** snappy as dev dep and typing example ([#1446](https://github.com/awslabs/aws-lambda-powertools-python/issues/1446)) -* **lambda-authorizer:** allow proxy resources path in arn ([#1051](https://github.com/awslabs/aws-lambda-powertools-python/issues/1051)) -* **license:** add MIT-0 license header ([#1871](https://github.com/awslabs/aws-lambda-powertools-python/issues/1871)) -* **license:** correction to MIT + MIT-0 (no proprietary anymore) ([#1883](https://github.com/awslabs/aws-lambda-powertools-python/issues/1883)) * **logger:** support exception and exception_name fields at any log level ([#1930](https://github.com/awslabs/aws-lambda-powertools-python/issues/1930)) -* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) -* **logger:** support additional args for handlers when injecting lambda context ([#1276](https://github.com/awslabs/aws-lambda-powertools-python/issues/1276)) -* **logger:** clear_state regression on absent standard keys ([#1088](https://github.com/awslabs/aws-lambda-powertools-python/issues/1088)) -* **logger:** clear_state should keep custom key formats ([#1095](https://github.com/awslabs/aws-lambda-powertools-python/issues/1095)) -* **logger:** fix unknown attributes being ignored by mypy ([#1670](https://github.com/awslabs/aws-lambda-powertools-python/issues/1670)) -* **logger:** exclude source_logger in copy_config_to_registered_loggers ([#1001](https://github.com/awslabs/aws-lambda-powertools-python/issues/1001)) -* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) -* **logger:** test generates logfile -* **logger:** ensure state is cleared for custom formatters ([#1072](https://github.com/awslabs/aws-lambda-powertools-python/issues/1072)) -* **logger-utils:** regression on exclude set leading to no formatter ([#1080](https://github.com/awslabs/aws-lambda-powertools-python/issues/1080)) * **metrics:** clarify no-metrics user warning ([#1935](https://github.com/awslabs/aws-lambda-powertools-python/issues/1935)) -* **metrics:** raise SchemaValidationError for >8 metric dimensions ([#1240](https://github.com/awslabs/aws-lambda-powertools-python/issues/1240)) -* **metrics:** ensure dimension_set is reused across instances (pointer) ([#1581](https://github.com/awslabs/aws-lambda-powertools-python/issues/1581)) -* **metrics:** explicit type to single_metric ctx manager ([#865](https://github.com/awslabs/aws-lambda-powertools-python/issues/865)) -* **metrics:** flush upon a single metric 100th data point ([#1046](https://github.com/awslabs/aws-lambda-powertools-python/issues/1046)) -* **middleware_factory:** ret type annotation for handler dec ([#1066](https://github.com/awslabs/aws-lambda-powertools-python/issues/1066)) -* **parameters:** get_secret correctly return SecretBinary value ([#1717](https://github.com/awslabs/aws-lambda-powertools-python/issues/1717)) -* **parameters:** appconfig internal _get docstrings ([#934](https://github.com/awslabs/aws-lambda-powertools-python/issues/934)) -* **parameters:** appconfig transform and return types ([#877](https://github.com/awslabs/aws-lambda-powertools-python/issues/877)) -* **parser:** body/QS can be null or omitted in apigw v1/v2 ([#820](https://github.com/awslabs/aws-lambda-powertools-python/issues/820)) -* **parser:** overload parse when using envelope ([#885](https://github.com/awslabs/aws-lambda-powertools-python/issues/885)) -* **parser:** kinesis sequence number is str, not int ([#907](https://github.com/awslabs/aws-lambda-powertools-python/issues/907)) -* **parser:** raise ValidationError when SNS->SQS keys are intentionally missing ([#1299](https://github.com/awslabs/aws-lambda-powertools-python/issues/1299)) -* **parser:** mypy support for payload type override as models ([#883](https://github.com/awslabs/aws-lambda-powertools-python/issues/883)) -* **parser:** loose validation on SNS fields to support FIFO ([#1606](https://github.com/awslabs/aws-lambda-powertools-python/issues/1606)) -* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) -* **parser:** S3Model Object Deleted omits size and eTag attr ([#1638](https://github.com/awslabs/aws-lambda-powertools-python/issues/1638)) -* **tests:** make sure multiple e2e tests run concurrently ([#1861](https://github.com/awslabs/aws-lambda-powertools-python/issues/1861)) -* **tests:** make logs fetching more robust ([#1878](https://github.com/awslabs/aws-lambda-powertools-python/issues/1878)) -* **tests:** remove custom workers -* **tracer:** add warm start annotation (ColdStart=False) ([#851](https://github.com/awslabs/aws-lambda-powertools-python/issues/851)) -* **typing:** level arg in copy_config_to_registered_loggers ([#1534](https://github.com/awslabs/aws-lambda-powertools-python/issues/1534)) -* **typing:** fix mypy error -* **warning:** future distutils deprecation ([#921](https://github.com/awslabs/aws-lambda-powertools-python/issues/921)) -## Code Refactoring +## Documentation -* rename to remove_custom_keys -* rename to clear_state -* **apigateway:** Add BaseRouter and duplicate route check ([#757](https://github.com/awslabs/aws-lambda-powertools-python/issues/757)) -* **apigateway:** remove POWERTOOLS_EVENT_HANDLER_DEBUG env var ([#1620](https://github.com/awslabs/aws-lambda-powertools-python/issues/1620)) -* **batch:** remove legacy sqs_batch_processor ([#1492](https://github.com/awslabs/aws-lambda-powertools-python/issues/1492)) -* **e2e:** make table name dynamic -* **e2e:** fix idempotency typing +* **event_handlers:** Fix REST API - HTTP Methods documentation ([#1936](https://github.com/awslabs/aws-lambda-powertools-python/issues/1936)) +* **home:** update powertools definition +* **we-made-this:** add CI/CD using Feature Flags video ([#1940](https://github.com/awslabs/aws-lambda-powertools-python/issues/1940)) +* **we-made-this:** add Feature Flags post ([#1939](https://github.com/awslabs/aws-lambda-powertools-python/issues/1939)) -## Documentation +## Features + +* **batch:** add support to SQS FIFO queues (SqsFifoPartialProcessor) ([#1934](https://github.com/awslabs/aws-lambda-powertools-python/issues/1934)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.5 to 2.1.0 ([#1943](https://github.com/awslabs/aws-lambda-powertools-python/issues/1943)) +* **deps:** bump pydantic from 1.10.4 to 1.10.5 ([#1931](https://github.com/awslabs/aws-lambda-powertools-python/issues/1931)) +* **deps-dev:** bump mkdocs-material from 9.0.12 to 9.0.13 ([#1944](https://github.com/awslabs/aws-lambda-powertools-python/issues/1944)) +* **deps-dev:** bump aws-cdk-lib from 2.64.0 to 2.65.0 ([#1938](https://github.com/awslabs/aws-lambda-powertools-python/issues/1938)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.6 to 2.8.19.7 ([#1932](https://github.com/awslabs/aws-lambda-powertools-python/issues/1932)) +* **deps-dev:** bump types-requests from 2.28.11.12 to 2.28.11.13 ([#1933](https://github.com/awslabs/aws-lambda-powertools-python/issues/1933)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.26.63 to 1.26.71 ([#1928](https://github.com/awslabs/aws-lambda-powertools-python/issues/1928)) +* **deps-dev:** bump flake8-bugbear from 23.1.20 to 23.2.13 ([#1924](https://github.com/awslabs/aws-lambda-powertools-python/issues/1924)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.26.0.post1 to 1.26.70 ([#1925](https://github.com/awslabs/aws-lambda-powertools-python/issues/1925)) + + + +## [v2.8.0] - 2023-02-10 +## Bug Fixes + +* **idempotency:** make idempotent_function decorator thread safe ([#1899](https://github.com/awslabs/aws-lambda-powertools-python/issues/1899)) + +## Documentation -* consistency around admonitions and snippets ([#919](https://github.com/awslabs/aws-lambda-powertools-python/issues/919)) -* disable search blur in non-prod env -* add amplify-cli instructions for public layer ([#754](https://github.com/awslabs/aws-lambda-powertools-python/issues/754)) -* improve public lambda layer wording, clipboard buttons ([#762](https://github.com/awslabs/aws-lambda-powertools-python/issues/762)) -* fix anchor -* fix indentation of SAM snippets in install section ([#778](https://github.com/awslabs/aws-lambda-powertools-python/issues/778)) -* external reference to cloudformation custom resource helper ([#914](https://github.com/awslabs/aws-lambda-powertools-python/issues/914)) -* rename to tutorial given the size -* updated Lambda Layers definition & limitations. ([#775](https://github.com/awslabs/aws-lambda-powertools-python/issues/775)) -* Idiomatic tenet updated to Progressive -* fix syntax errors and line highlights ([#1004](https://github.com/awslabs/aws-lambda-powertools-python/issues/1004)) -* update Lambda Layers version -* add better BDD coments -* Added GraphQL Sample API to Examples section of README.md ([#930](https://github.com/awslabs/aws-lambda-powertools-python/issues/930)) -* project name consistency -* rename quickstart to tutorial in readme -* use higher contrast font -* use higher contrast font ([#822](https://github.com/awslabs/aws-lambda-powertools-python/issues/822)) -* add final consideration section -* add new public Slack invite -* **api-gateway:** add support for new router feature ([#767](https://github.com/awslabs/aws-lambda-powertools-python/issues/767)) -* **apigateway:** add all resolvers in testing your code section for accuracy ([#1688](https://github.com/awslabs/aws-lambda-powertools-python/issues/1688)) -* **apigateway:** add new not_found feature ([#915](https://github.com/awslabs/aws-lambda-powertools-python/issues/915)) -* **apigateway:** re-add sample layout, add considerations ([#826](https://github.com/awslabs/aws-lambda-powertools-python/issues/826)) -* **apigateway:** removes duplicate admonition ([#1426](https://github.com/awslabs/aws-lambda-powertools-python/issues/1426)) -* **apigateway:** fix sample layout provided ([#864](https://github.com/awslabs/aws-lambda-powertools-python/issues/864)) -* **appsync:** fix users.py typo to locations [#830](https://github.com/awslabs/aws-lambda-powertools-python/issues/830) -* **appsync:** add mutation example and infrastructure fix ([#1964](https://github.com/awslabs/aws-lambda-powertools-python/issues/1964)) -* **appsync:** add new router feature ([#821](https://github.com/awslabs/aws-lambda-powertools-python/issues/821)) -* **appsync:** fix typo -* **batch:** snippet typo on batch processed messages iteration ([#951](https://github.com/awslabs/aws-lambda-powertools-python/issues/951)) -* **batch:** remove leftover from legacy -* **batch:** document the new lambda context feature -* **batch:** remove legacy reference to sqs processor -* **batch:** fix typo in context manager keyword ([#938](https://github.com/awslabs/aws-lambda-powertools-python/issues/938)) -* **community:** fix social handlers for Ran ([#1654](https://github.com/awslabs/aws-lambda-powertools-python/issues/1654)) -* **community:** fix twitch parent domain for embedded video -* **contributing:** operational excellence pause * **engine:** re-enable clipboard button for code snippets -* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) -* **event-handler:** improve testing section for graphql ([#996](https://github.com/awslabs/aws-lambda-powertools-python/issues/996)) -* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) -* **event-source:** fix incorrect method in example CloudWatch Logs ([#1857](https://github.com/awslabs/aws-lambda-powertools-python/issues/1857)) -* **event_handlers:** Fix REST API - HTTP Methods documentation ([#1936](https://github.com/awslabs/aws-lambda-powertools-python/issues/1936)) -* **examples:** linting unnecessary whitespace -* **examples:** enforce and fix all mypy errors ([#1393](https://github.com/awslabs/aws-lambda-powertools-python/issues/1393)) -* **governance:** allow community to suggest feature content ([#1593](https://github.com/awslabs/aws-lambda-powertools-python/issues/1593)) -* **governance:** new form to allow customers self-nominate as public reference ([#1589](https://github.com/awslabs/aws-lambda-powertools-python/issues/1589)) -* **governance:** add security doc to the root -* **governance:** typos on PR template fixes [#1314](https://github.com/awslabs/aws-lambda-powertools-python/issues/1314) -* **governance:** link roadmap and maintainers doc -* **graphql:** snippets split, improved, and lint ([#1287](https://github.com/awslabs/aws-lambda-powertools-python/issues/1287)) -* **home:** update powertools definition -* **home:** add discord invitation link ([#1471](https://github.com/awslabs/aws-lambda-powertools-python/issues/1471)) -* **home:** fix discord syntax and add Discord badge -* **homepage:** include .NET powertools -* **homepage:** update default value for `POWERTOOLS_DEV` ([#1695](https://github.com/awslabs/aws-lambda-powertools-python/issues/1695)) -* **homepage:** add banner for end-of-support v1 ([#1879](https://github.com/awslabs/aws-lambda-powertools-python/issues/1879)) * **homepage:** Replace poetry command to add group parameter ([#1917](https://github.com/awslabs/aws-lambda-powertools-python/issues/1917)) -* **homepage:** link to typescript version ([#950](https://github.com/awslabs/aws-lambda-powertools-python/issues/950)) * **homepage:** set url for end-of-support in announce block ([#1893](https://github.com/awslabs/aws-lambda-powertools-python/issues/1893)) -* **homepage:** remove v1 layer limitation on pydantic not being included -* **homepage:** note about v2 version -* **homepage:** emphasize additional powertools languages ([#1292](https://github.com/awslabs/aws-lambda-powertools-python/issues/1292)) -* **homepage:** remove 3.6 and add hero image -* **homepage:** introduce POWERTOOLS_DEV env var ([#1569](https://github.com/awslabs/aws-lambda-powertools-python/issues/1569)) -* **homepage:** revamp install UX & share how we build Lambda Layer ([#1978](https://github.com/awslabs/aws-lambda-powertools-python/issues/1978)) -* **homepage:** auto-update Layer ARN on every release ([#1610](https://github.com/awslabs/aws-lambda-powertools-python/issues/1610)) -* **homepage:** add Pulumi code example ([#1652](https://github.com/awslabs/aws-lambda-powertools-python/issues/1652)) -* **idempotency:** fix register_lambda_context order ([#1747](https://github.com/awslabs/aws-lambda-powertools-python/issues/1747)) -* **idempotency:** add missing Lambda Context; note on thread-safe ([#1732](https://github.com/awslabs/aws-lambda-powertools-python/issues/1732)) -* **idempotency:** add support for DynamoDB composite keys ([#808](https://github.com/awslabs/aws-lambda-powertools-python/issues/808)) -* **idempotency:** "persisntence" typo ([#1596](https://github.com/awslabs/aws-lambda-powertools-python/issues/1596)) * **idempotency:** add IAM permissions section ([#1902](https://github.com/awslabs/aws-lambda-powertools-python/issues/1902)) -* **idempotency:** fix, improve, and increase visibility for batch integration ([#1776](https://github.com/awslabs/aws-lambda-powertools-python/issues/1776)) -* **index:** fold support us banner -* **index:** add quotes to pip for zsh customers -* **install:** new lambda layer for 1.24.0 release -* **install:** instructions to reduce pydantic package size ([#1077](https://github.com/awslabs/aws-lambda-powertools-python/issues/1077)) -* **install:** address early v2 feedback on installation and project support -* **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) -* **lambda_layer:** fix CDK layer syntax -* **layer:** upgrade to 1.25.10 -* **layer:** upgrade to 1.26.7 -* **layer:** upgrade to 1.27.0 -* **layer:** bump Lambda Layer to version 6 -* **layer:** upgrade to 1.25.9 -* **layer:** remove link from clipboard button ([#1135](https://github.com/awslabs/aws-lambda-powertools-python/issues/1135)) -* **layer:** update to 1.24.2 -* **layer:** update to 1.25.7 -* **layer:** update to 1.25.6; cosmetic changes -* **layer:** bump to 1.25.5 -* **layer:** upgrade to 1.27.0 -* **layer:** update to 1.24.1 -* **layer:** update to 1.25.3 -* **layer:** update to 1.25.1 -* **layer:** upgrade to 1.28.0 (v33) -* **lint:** add markdownlint rules and automation ([#1256](https://github.com/awslabs/aws-lambda-powertools-python/issues/1256)) -* **logger:** fix typo. ([#1587](https://github.com/awslabs/aws-lambda-powertools-python/issues/1587)) -* **logger:** Add warning of uncaught exceptions ([#1826](https://github.com/awslabs/aws-lambda-powertools-python/issues/1826)) -* **logger:** document enriching logs with logrecord attributes ([#1271](https://github.com/awslabs/aws-lambda-powertools-python/issues/1271)) -* **logger:** fix incorrect field names in example structured logs ([#1830](https://github.com/awslabs/aws-lambda-powertools-python/issues/1830)) -* **logger:** snippets split, improved, and lint ([#1262](https://github.com/awslabs/aws-lambda-powertools-python/issues/1262)) -* **logger:** update uncaught exception message value -* **maintainers:** initial maintainers playbook ([#1222](https://github.com/awslabs/aws-lambda-powertools-python/issues/1222)) * **metrics:** remove reduntant wording before release * **metrics:** fix syntax highlighting for new default_dimensions -* **metrics:** snippets split, improved, and lint -* **metrics:** snippets split, improved, and lint ([#1272](https://github.com/awslabs/aws-lambda-powertools-python/issues/1272)) -* **metrics:** keep it consistent with other sections, update metric names -* **middleware-factory:** snippets split, improved, and lint ([#1451](https://github.com/awslabs/aws-lambda-powertools-python/issues/1451)) -* **multiple:** fix highlighting after new isort/black integration -* **nav:** make REST and GraphQL event handlers more explicit ([#959](https://github.com/awslabs/aws-lambda-powertools-python/issues/959)) -* **parameters:** fix typos and inconsistencies ([#1966](https://github.com/awslabs/aws-lambda-powertools-python/issues/1966)) -* **parameters:** snippets split, improved, and lint ([#1564](https://github.com/awslabs/aws-lambda-powertools-python/issues/1564)) -* **parameters:** add testing your code section ([#1017](https://github.com/awslabs/aws-lambda-powertools-python/issues/1017)) -* **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) -* **parser:** add JSON string field extension example ([#1526](https://github.com/awslabs/aws-lambda-powertools-python/issues/1526)) -* **parser:** APIGatewayProxyEvent to APIGatewayProxyEventModel ([#1061](https://github.com/awslabs/aws-lambda-powertools-python/issues/1061)) -* **plugin:** add mermaid to create diagram as code ([#1070](https://github.com/awslabs/aws-lambda-powertools-python/issues/1070)) -* **quickstart:** expand on intro line -* **quickstart:** sentence fragmentation, tidy up -* **quickstart:** tidy requirements up -* **quickstart:** add sub-sections, fix highlight & code -* **quickstart:** same process for Logger -* **quickstart:** make section agnostic to json lib -* **readme:** add lambda layer latest version badge -* **roadmap:** add new roadmap section ([#1204](https://github.com/awslabs/aws-lambda-powertools-python/issues/1204)) -* **roadmap:** use pinned pause issue instead -* **roadmap:** include observability provider and lambda layer themes before v2 -* **roadmap:** refresh roadmap post-v2 launch -* **streaming:** fix leftover newline -* **tenets:** update Idiomatic tenet to Progressive ([#823](https://github.com/awslabs/aws-lambda-powertools-python/issues/823)) -* **tenets:** make core, non-core more explicit -* **theme:** upgrade mkdocs-material to 8.x ([#1002](https://github.com/awslabs/aws-lambda-powertools-python/issues/1002)) -* **tracer:** snippets split, improved, and lint ([#1261](https://github.com/awslabs/aws-lambda-powertools-python/issues/1261)) -* **tracer:** update ServiceLens image w/ API GW, copywriting -* **tracer:** new ignore_endpoint feature ([#931](https://github.com/awslabs/aws-lambda-powertools-python/issues/931)) -* **tracer:** add annotation, metadata, and image -* **tracer:** add note on why X-Ray SDK over ADOT closes [#1675](https://github.com/awslabs/aws-lambda-powertools-python/issues/1675) -* **tracer:** add initial image, requirements -* **tracer:** warning to note on local traces -* **tracer:** split and lint code snippets ([#1260](https://github.com/awslabs/aws-lambda-powertools-python/issues/1260)) -* **tutorial:** fix path to images ([#963](https://github.com/awslabs/aws-lambda-powertools-python/issues/963)) -* **tutorial:** fix broken internal links ([#1000](https://github.com/awslabs/aws-lambda-powertools-python/issues/1000)) -* **typing:** snippets split, improved, and lint ([#1465](https://github.com/awslabs/aws-lambda-powertools-python/issues/1465)) -* **upgrade_guide:** add latest changes and quick summary ([#1623](https://github.com/awslabs/aws-lambda-powertools-python/issues/1623)) -* **v2:** document optional dependencies and local dev ([#1574](https://github.com/awslabs/aws-lambda-powertools-python/issues/1574)) -* **validation:** snippets split, improved, and lint ([#1449](https://github.com/awslabs/aws-lambda-powertools-python/issues/1449)) -* **validation:** fix broken link; enrich built-in jmespath links ([#1777](https://github.com/awslabs/aws-lambda-powertools-python/issues/1777)) -* **we-made-this:** new community content section ([#1650](https://github.com/awslabs/aws-lambda-powertools-python/issues/1650)) -* **we-made-this:** add Feature Flags post ([#1939](https://github.com/awslabs/aws-lambda-powertools-python/issues/1939)) -* **we-made-this:** add CI/CD using Feature Flags video ([#1940](https://github.com/awslabs/aws-lambda-powertools-python/issues/1940)) ## Features -* **apigateway:** multiple exceptions in exception_handler ([#1707](https://github.com/awslabs/aws-lambda-powertools-python/issues/1707)) -* **apigateway:** add Router to allow large routing composition ([#645](https://github.com/awslabs/aws-lambda-powertools-python/issues/645)) -* **apigateway:** ignore trailing slashes in routes (APIGatewayRestResolver) ([#1609](https://github.com/awslabs/aws-lambda-powertools-python/issues/1609)) -* **apigateway:** access parent api resolver from router ([#842](https://github.com/awslabs/aws-lambda-powertools-python/issues/842)) -* **apigateway:** add exception_handler support ([#898](https://github.com/awslabs/aws-lambda-powertools-python/issues/898)) -* **appsync:** add Router to allow large resolver composition ([#776](https://github.com/awslabs/aws-lambda-powertools-python/issues/776)) * **batch:** add async_batch_processor for concurrent processing ([#1724](https://github.com/awslabs/aws-lambda-powertools-python/issues/1724)) -* **batch:** new BatchProcessor for SQS, DynamoDB, Kinesis ([#886](https://github.com/awslabs/aws-lambda-powertools-python/issues/886)) -* **batch:** add support to SQS FIFO queues (SqsFifoPartialProcessor) ([#1934](https://github.com/awslabs/aws-lambda-powertools-python/issues/1934)) -* **batch:** inject lambda_context if record handler signature accepts it ([#1561](https://github.com/awslabs/aws-lambda-powertools-python/issues/1561)) -* **ci:** release docs as alpha when doing a pre-release ([#1624](https://github.com/awslabs/aws-lambda-powertools-python/issues/1624)) -* **ci:** auto-notify & close issues on release -* **ci:** create reusable changelog generation -* **ci:** include changelog generation on docs build -* **ci:** create reusable changelog generation ([#1418](https://github.com/awslabs/aws-lambda-powertools-python/issues/1418)) -* **ci:** add actionlint in pre-commit hook -* **data-classes:** add KafkaEvent and KafkaEventRecord ([#1485](https://github.com/awslabs/aws-lambda-powertools-python/issues/1485)) -* **data-classes:** replace AttributeValue in DynamoDBStreamEvent with deserialized Python values ([#1619](https://github.com/awslabs/aws-lambda-powertools-python/issues/1619)) -* **data-classes:** ActiveMQ and RabbitMQ support ([#770](https://github.com/awslabs/aws-lambda-powertools-python/issues/770)) -* **data_classes:** add KinesisFirehoseEvent ([#1540](https://github.com/awslabs/aws-lambda-powertools-python/issues/1540)) -* **event-handler:** new resolvers to fix current_event typing ([#978](https://github.com/awslabs/aws-lambda-powertools-python/issues/978)) -* **event-handler:** context support to share data between routers ([#1567](https://github.com/awslabs/aws-lambda-powertools-python/issues/1567)) -* **event-sources:** cache parsed json in data class ([#909](https://github.com/awslabs/aws-lambda-powertools-python/issues/909)) -* **event_handler:** improved support for headers and cookies in v2 ([#1455](https://github.com/awslabs/aws-lambda-powertools-python/issues/1455)) -* **event_handler:** add cookies as 1st class citizen in v2 ([#1487](https://github.com/awslabs/aws-lambda-powertools-python/issues/1487)) -* **event_handlers:** Add support for Lambda Function URLs ([#1408](https://github.com/awslabs/aws-lambda-powertools-python/issues/1408)) -* **event_sources:** extract CloudWatch Logs in Kinesis streams ([#1710](https://github.com/awslabs/aws-lambda-powertools-python/issues/1710)) -* **event_sources:** add CloudWatch dashboard custom widget event ([#1474](https://github.com/awslabs/aws-lambda-powertools-python/issues/1474)) -* **feature_flags:** Add Time based feature flags actions ([#1846](https://github.com/awslabs/aws-lambda-powertools-python/issues/1846)) -* **feature_flags:** support beyond boolean values (JSON values) ([#804](https://github.com/awslabs/aws-lambda-powertools-python/issues/804)) -* **idempotency:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1535](https://github.com/awslabs/aws-lambda-powertools-python/issues/1535)) -* **idempotency:** support dataclasses & pydantic models payloads ([#908](https://github.com/awslabs/aws-lambda-powertools-python/issues/908)) -* **idempotency:** handle lambda timeout scenarios for INPROGRESS records ([#1387](https://github.com/awslabs/aws-lambda-powertools-python/issues/1387)) -* **layer:** publish SAR v2 via Github actions ([#1585](https://github.com/awslabs/aws-lambda-powertools-python/issues/1585)) -* **layers:** add layer balancer script ([#1643](https://github.com/awslabs/aws-lambda-powertools-python/issues/1643)) -* **layers:** add support for publishing v2 layer ([#1558](https://github.com/awslabs/aws-lambda-powertools-python/issues/1558)) -* **logger:** clone powertools logger config to any Python logger ([#927](https://github.com/awslabs/aws-lambda-powertools-python/issues/927)) -* **logger:** log uncaught exceptions via system's exception hook ([#1727](https://github.com/awslabs/aws-lambda-powertools-python/issues/1727)) -* **logger:** allow handler with custom kwargs signature ([#913](https://github.com/awslabs/aws-lambda-powertools-python/issues/913)) -* **logger:** accept arbitrary keyword=value for ephemeral metadata ([#1658](https://github.com/awslabs/aws-lambda-powertools-python/issues/1658)) -* **logger:** add use_rfc3339 and auto-complete formatter opts in Logger ([#1662](https://github.com/awslabs/aws-lambda-powertools-python/issues/1662)) -* **logger:** add ALB correlation ID support ([#816](https://github.com/awslabs/aws-lambda-powertools-python/issues/816)) -* **logger:** unwrap event from common models if asked to log ([#1778](https://github.com/awslabs/aws-lambda-powertools-python/issues/1778)) -* **logger:** pretty-print JSON when POWERTOOLS_DEV is set ([#1548](https://github.com/awslabs/aws-lambda-powertools-python/issues/1548)) -* **logger:** include logger name attribute when copy_config_to_registered_logger is used ([#1568](https://github.com/awslabs/aws-lambda-powertools-python/issues/1568)) -* **logger:** introduce POWERTOOLS_DEBUG for internal debugging ([#1572](https://github.com/awslabs/aws-lambda-powertools-python/issues/1572)) -* **logger:** support use_datetime_directive for timestamps ([#920](https://github.com/awslabs/aws-lambda-powertools-python/issues/920)) -* **logger:** log_event support event data classes (e.g. S3Event) ([#984](https://github.com/awslabs/aws-lambda-powertools-python/issues/984)) -* **logger:** add option to clear state per invocation -* **metrics:** update max user-defined dimensions from 9 to 29 ([#1417](https://github.com/awslabs/aws-lambda-powertools-python/issues/1417)) -* **metrics:** add EphemeralMetrics as a non-singleton option ([#1676](https://github.com/awslabs/aws-lambda-powertools-python/issues/1676)) * **metrics:** add default_dimensions to single_metric ([#1880](https://github.com/awslabs/aws-lambda-powertools-python/issues/1880)) -* **mypy:** complete mypy support for the entire codebase ([#943](https://github.com/awslabs/aws-lambda-powertools-python/issues/943)) -* **parameters:** add get_parameters_by_name for SSM params in distinct paths ([#1678](https://github.com/awslabs/aws-lambda-powertools-python/issues/1678)) -* **parameters:** accept boto3_client to support private endpoints and ease testing ([#1096](https://github.com/awslabs/aws-lambda-powertools-python/issues/1096)) -* **parameters:** migrate AppConfig to new APIs due to API deprecation ([#1553](https://github.com/awslabs/aws-lambda-powertools-python/issues/1553)) -* **parameters:** add clear_cache method for providers ([#1194](https://github.com/awslabs/aws-lambda-powertools-python/issues/1194)) -* **parser:** add KinesisFirehoseModel ([#1556](https://github.com/awslabs/aws-lambda-powertools-python/issues/1556)) -* **parser:** export Pydantic.errors through escape hatch ([#1728](https://github.com/awslabs/aws-lambda-powertools-python/issues/1728)) -* **parser:** add support for Lambda Function URL ([#1442](https://github.com/awslabs/aws-lambda-powertools-python/issues/1442)) -* **parser:** extract CloudWatch Logs in Kinesis streams ([#1726](https://github.com/awslabs/aws-lambda-powertools-python/issues/1726)) -* **parser:** add KafkaMskEventModel and KafkaSelfManagedEventModel ([#1499](https://github.com/awslabs/aws-lambda-powertools-python/issues/1499)) -* **streaming:** add new s3 streaming utility ([#1719](https://github.com/awslabs/aws-lambda-powertools-python/issues/1719)) -* **tracer:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1486](https://github.com/awslabs/aws-lambda-powertools-python/issues/1486)) -* **tracer:** ignore tracing for certain hostname(s) or url(s) ([#910](https://github.com/awslabs/aws-lambda-powertools-python/issues/910)) -* **tracer:** add service annotation when service is set ([#861](https://github.com/awslabs/aws-lambda-powertools-python/issues/861)) ## Maintenance * update v2 layer ARN on documentation -* remove Lambda Layer version tag -* conditional to publish docs only -* conditional to publish docs only attempt 2 -* conditional to publish docs only attempt 3 -* bump to 1.22.0 -* update v2 layer ARN on documentation -* correct pr label order -* minor housekeeping before release ([#912](https://github.com/awslabs/aws-lambda-powertools-python/issues/912)) -* bump to 1.23.0 -* bump to 1.24.0 -* apigw test event wrongly set with base64 -* update v2 layer ARN on documentation -* update v2 layer ARN on documentation -* update v2 layer ARN on documentation -* update v2 layer ARN on documentation -* update v2 layer ARN on documentation -* update v2 layer ARN on documentation -* update project description -* merge v2 branch -* bump to 1.24.1 -* bump pyproject version to 2.0 -* bump to 1.24.1 -* bump to 1.24.2 -* use isinstance over type -* correct docs -* correct docs -* bump to 1.25.0 -* bump to 1.25.1 -* bump to 1.25.2 -* bump to 1.25.3 -* lint unused import -* remove unnecessary test -* comment reason for change -* remove duplicate test -* bump to 1.25.4 -* update v2 layer ARN on documentation -* bump to 1.25.5 -* bump to 1.25.6 -* bump to 1.25.7 -* bump to 1.25.8 -* bump to 1.25.9 -* add dummy v2 sar deploy job +* **deps:** bump docker/setup-buildx-action from 2.4.0 to 2.4.1 ([#1903](https://github.com/awslabs/aws-lambda-powertools-python/issues/1903)) +* **deps-dev:** bump aws-cdk-lib from 2.63.0 to 2.63.2 ([#1904](https://github.com/awslabs/aws-lambda-powertools-python/issues/1904)) +* **deps-dev:** bump black from 22.12.0 to 23.1.0 ([#1886](https://github.com/awslabs/aws-lambda-powertools-python/issues/1886)) +* **deps-dev:** bump types-requests from 2.28.11.8 to 2.28.11.12 ([#1906](https://github.com/awslabs/aws-lambda-powertools-python/issues/1906)) +* **deps-dev:** bump pytest-xdist from 3.1.0 to 3.2.0 ([#1905](https://github.com/awslabs/aws-lambda-powertools-python/issues/1905)) +* **deps-dev:** bump aws-cdk-lib from 2.63.2 to 2.64.0 ([#1918](https://github.com/awslabs/aws-lambda-powertools-python/issues/1918)) +* **deps-dev:** bump mkdocs-material from 9.0.11 to 9.0.12 ([#1919](https://github.com/awslabs/aws-lambda-powertools-python/issues/1919)) +* **deps-dev:** bump mkdocs-material from 9.0.10 to 9.0.11 ([#1896](https://github.com/awslabs/aws-lambda-powertools-python/issues/1896)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.26.0.post1 to 1.26.63 ([#1895](https://github.com/awslabs/aws-lambda-powertools-python/issues/1895)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.58 to 1.26.62 ([#1889](https://github.com/awslabs/aws-lambda-powertools-python/issues/1889)) +* **deps-dev:** bump mkdocs-material from 9.0.9 to 9.0.10 ([#1888](https://github.com/awslabs/aws-lambda-powertools-python/issues/1888)) +* **deps-dev:** bump aws-cdk-lib from 2.62.2 to 2.63.0 ([#1887](https://github.com/awslabs/aws-lambda-powertools-python/issues/1887)) +* **maintainers:** fix release workflow rename +* **pypi:** add new links to Pypi package homepage ([#1912](https://github.com/awslabs/aws-lambda-powertools-python/issues/1912)) + + + +## [v2.7.1] - 2023-02-01 +## Bug Fixes + +* parallel_run should fail when e2e tests fail +* bump aws-cdk version +* **ci:** scope e2e tests by python version +* **ci:** add auth to API HTTP Gateway and Lambda Function Url ([#1882](https://github.com/awslabs/aws-lambda-powertools-python/issues/1882)) +* **license:** correction to MIT + MIT-0 (no proprietary anymore) ([#1883](https://github.com/awslabs/aws-lambda-powertools-python/issues/1883)) +* **license:** add MIT-0 license header ([#1871](https://github.com/awslabs/aws-lambda-powertools-python/issues/1871)) +* **tests:** make logs fetching more robust ([#1878](https://github.com/awslabs/aws-lambda-powertools-python/issues/1878)) +* **tests:** remove custom workers +* **tests:** make sure multiple e2e tests run concurrently ([#1861](https://github.com/awslabs/aws-lambda-powertools-python/issues/1861)) + +## Documentation + +* **event-source:** fix incorrect method in example CloudWatch Logs ([#1857](https://github.com/awslabs/aws-lambda-powertools-python/issues/1857)) +* **homepage:** add banner for end-of-support v1 ([#1879](https://github.com/awslabs/aws-lambda-powertools-python/issues/1879)) +* **parameters:** snippets split, improved, and lint ([#1564](https://github.com/awslabs/aws-lambda-powertools-python/issues/1564)) + +## Maintenance + * update v2 layer ARN on documentation -* bump layer version to 38 -* bump to 1.25.10 +* **deps:** bump docker/setup-buildx-action from 2.0.0 to 2.4.0 ([#1873](https://github.com/awslabs/aws-lambda-powertools-python/issues/1873)) +* **deps:** bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 ([#1855](https://github.com/awslabs/aws-lambda-powertools-python/issues/1855)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.0.post1 to 1.26.58 ([#1868](https://github.com/awslabs/aws-lambda-powertools-python/issues/1868)) +* **deps-dev:** bump isort from 5.11.4 to 5.11.5 ([#1875](https://github.com/awslabs/aws-lambda-powertools-python/issues/1875)) +* **deps-dev:** bump aws-cdk-lib from 2.62.1 to 2.62.2 ([#1869](https://github.com/awslabs/aws-lambda-powertools-python/issues/1869)) +* **deps-dev:** bump mkdocs-material from 9.0.6 to 9.0.8 ([#1874](https://github.com/awslabs/aws-lambda-powertools-python/issues/1874)) +* **deps-dev:** bump aws-cdk-lib from 2.62.0 to 2.62.1 ([#1866](https://github.com/awslabs/aws-lambda-powertools-python/issues/1866)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.35.post1 to 1.26.57 ([#1865](https://github.com/awslabs/aws-lambda-powertools-python/issues/1865)) +* **deps-dev:** bump coverage from 7.0.5 to 7.1.0 ([#1862](https://github.com/awslabs/aws-lambda-powertools-python/issues/1862)) +* **deps-dev:** bump aws-cdk-lib from 2.61.1 to 2.62.0 ([#1863](https://github.com/awslabs/aws-lambda-powertools-python/issues/1863)) +* **deps-dev:** bump flake8-bugbear from 22.12.6 to 23.1.20 ([#1854](https://github.com/awslabs/aws-lambda-powertools-python/issues/1854)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.49 to 1.26.55 ([#1856](https://github.com/awslabs/aws-lambda-powertools-python/issues/1856)) + +## Reverts +* fix(tests): remove custom workers + + + +## [v2.7.0] - 2023-01-24 +## Bug Fixes + +* git-chlg docker image is broken + +## Features + +* **feature_flags:** Add Time based feature flags actions ([#1846](https://github.com/awslabs/aws-lambda-powertools-python/issues/1846)) + +## Maintenance + * update v2 layer ARN on documentation -* include regression in changelog -* bump to 1.26.0 -* bump version 1.26.1 -* print full event depth -* add sam build gitignore -* move to approach B for multiple IaC -* test build layer hardware to 8 core +* **deps:** bump peaceiris/actions-gh-pages from 3.9.1 to 3.9.2 ([#1841](https://github.com/awslabs/aws-lambda-powertools-python/issues/1841)) +* **deps:** bump future from 0.18.2 to 0.18.3 ([#1836](https://github.com/awslabs/aws-lambda-powertools-python/issues/1836)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.4 to 2.0.5 ([#1837](https://github.com/awslabs/aws-lambda-powertools-python/issues/1837)) +* **deps-dev:** bump mkdocs-material from 9.0.4 to 9.0.5 ([#1840](https://github.com/awslabs/aws-lambda-powertools-python/issues/1840)) +* **deps-dev:** bump types-requests from 2.28.11.7 to 2.28.11.8 ([#1843](https://github.com/awslabs/aws-lambda-powertools-python/issues/1843)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.30 to 1.26.52 ([#1847](https://github.com/awslabs/aws-lambda-powertools-python/issues/1847)) +* **deps-dev:** bump pytest from 7.2.0 to 7.2.1 ([#1838](https://github.com/awslabs/aws-lambda-powertools-python/issues/1838)) +* **deps-dev:** bump aws-cdk-lib from 2.60.0 to 2.61.1 ([#1849](https://github.com/awslabs/aws-lambda-powertools-python/issues/1849)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.49 to 1.26.53 ([#1850](https://github.com/awslabs/aws-lambda-powertools-python/issues/1850)) +* **deps-dev:** bump mkdocs-material from 9.0.5 to 9.0.6 ([#1851](https://github.com/awslabs/aws-lambda-powertools-python/issues/1851)) +* **deps-dev:** bump mkdocs-material from 9.0.3 to 9.0.4 ([#1833](https://github.com/awslabs/aws-lambda-powertools-python/issues/1833)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.43 to 1.26.49 ([#1834](https://github.com/awslabs/aws-lambda-powertools-python/issues/1834)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.40 to 1.26.49 ([#1835](https://github.com/awslabs/aws-lambda-powertools-python/issues/1835)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.18 to 1.26.49 ([#1832](https://github.com/awslabs/aws-lambda-powertools-python/issues/1832)) +* **deps-dev:** bump aws-cdk-lib from 2.59.0 to 2.60.0 ([#1831](https://github.com/awslabs/aws-lambda-powertools-python/issues/1831)) + + + +## [v2.6.0] - 2023-01-12 +## Bug Fixes + +* **api_gateway:** fixed custom metrics issue when using debug mode ([#1827](https://github.com/awslabs/aws-lambda-powertools-python/issues/1827)) + +## Documentation + +* **logger:** fix incorrect field names in example structured logs ([#1830](https://github.com/awslabs/aws-lambda-powertools-python/issues/1830)) +* **logger:** Add warning of uncaught exceptions ([#1826](https://github.com/awslabs/aws-lambda-powertools-python/issues/1826)) + +## Maintenance + * update v2 layer ARN on documentation +* **deps:** bump pydantic from 1.10.2 to 1.10.4 ([#1817](https://github.com/awslabs/aws-lambda-powertools-python/issues/1817)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.1 to 2.0.3 ([#1801](https://github.com/awslabs/aws-lambda-powertools-python/issues/1801)) +* **deps:** bump release-drafter/release-drafter from 5.21.1 to 5.22.0 ([#1802](https://github.com/awslabs/aws-lambda-powertools-python/issues/1802)) +* **deps:** bump gitpython from 3.1.29 to 3.1.30 ([#1812](https://github.com/awslabs/aws-lambda-powertools-python/issues/1812)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.3 to 2.0.4 ([#1821](https://github.com/awslabs/aws-lambda-powertools-python/issues/1821)) +* **deps:** bump peaceiris/actions-gh-pages from 3.9.0 to 3.9.1 ([#1814](https://github.com/awslabs/aws-lambda-powertools-python/issues/1814)) +* **deps-dev:** bump mkdocs-material from 8.5.11 to 9.0.2 ([#1808](https://github.com/awslabs/aws-lambda-powertools-python/issues/1808)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.11.post1 to 1.26.43 ([#1819](https://github.com/awslabs/aws-lambda-powertools-python/issues/1819)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.27 to 1.26.43 ([#1820](https://github.com/awslabs/aws-lambda-powertools-python/issues/1820)) +* **deps-dev:** bump filelock from 3.8.2 to 3.9.0 ([#1816](https://github.com/awslabs/aws-lambda-powertools-python/issues/1816)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.11.post1 to 1.26.35.post1 ([#1818](https://github.com/awslabs/aws-lambda-powertools-python/issues/1818)) +* **deps-dev:** bump ijson from 3.1.4 to 3.2.0.post0 ([#1815](https://github.com/awslabs/aws-lambda-powertools-python/issues/1815)) +* **deps-dev:** bump coverage from 6.5.0 to 7.0.3 ([#1806](https://github.com/awslabs/aws-lambda-powertools-python/issues/1806)) +* **deps-dev:** bump flake8-builtins from 2.0.1 to 2.1.0 ([#1799](https://github.com/awslabs/aws-lambda-powertools-python/issues/1799)) +* **deps-dev:** bump coverage from 7.0.3 to 7.0.4 ([#1822](https://github.com/awslabs/aws-lambda-powertools-python/issues/1822)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.12 to 1.26.40 ([#1811](https://github.com/awslabs/aws-lambda-powertools-python/issues/1811)) +* **deps-dev:** bump isort from 5.11.3 to 5.11.4 ([#1809](https://github.com/awslabs/aws-lambda-powertools-python/issues/1809)) +* **deps-dev:** bump aws-cdk-lib from 2.55.1 to 2.59.0 ([#1810](https://github.com/awslabs/aws-lambda-powertools-python/issues/1810)) +* **deps-dev:** bump importlib-metadata from 5.1.0 to 6.0.0 ([#1804](https://github.com/awslabs/aws-lambda-powertools-python/issues/1804)) +* **deps-dev:** bump mkdocs-material from 9.0.2 to 9.0.3 ([#1823](https://github.com/awslabs/aws-lambda-powertools-python/issues/1823)) +* **deps-dev:** bump black from 22.10.0 to 22.12.0 ([#1770](https://github.com/awslabs/aws-lambda-powertools-python/issues/1770)) +* **deps-dev:** bump flake8-black from 0.3.5 to 0.3.6 ([#1792](https://github.com/awslabs/aws-lambda-powertools-python/issues/1792)) +* **deps-dev:** bump coverage from 7.0.4 to 7.0.5 ([#1829](https://github.com/awslabs/aws-lambda-powertools-python/issues/1829)) +* **deps-dev:** bump types-requests from 2.28.11.5 to 2.28.11.7 ([#1795](https://github.com/awslabs/aws-lambda-powertools-python/issues/1795)) + + + +## [v2.5.0] - 2022-12-21 +## Bug Fixes + +* **event_handlers:** omit explicit None HTTP header values ([#1793](https://github.com/awslabs/aws-lambda-powertools-python/issues/1793)) + +## Documentation + +* **idempotency:** fix, improve, and increase visibility for batch integration ([#1776](https://github.com/awslabs/aws-lambda-powertools-python/issues/1776)) +* **validation:** fix broken link; enrich built-in jmespath links ([#1777](https://github.com/awslabs/aws-lambda-powertools-python/issues/1777)) + +## Features + +* **logger:** unwrap event from common models if asked to log ([#1778](https://github.com/awslabs/aws-lambda-powertools-python/issues/1778)) + +## Maintenance + * update v2 layer ARN on documentation -* bump to version 1.26.3 -* fix var expr -* dummy for PR test +* **common:** reusable function to extract event from models +* **deps:** bump certifi from 2022.9.24 to 2022.12.7 ([#1768](https://github.com/awslabs/aws-lambda-powertools-python/issues/1768)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.4.0 to 2.0.1 ([#1752](https://github.com/awslabs/aws-lambda-powertools-python/issues/1752)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.3.0 to 1.4.0 ([#1749](https://github.com/awslabs/aws-lambda-powertools-python/issues/1749)) +* **deps-dev:** bump pytest-asyncio from 0.20.2 to 0.20.3 ([#1767](https://github.com/awslabs/aws-lambda-powertools-python/issues/1767)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.0.post1 to 1.26.17 ([#1753](https://github.com/awslabs/aws-lambda-powertools-python/issues/1753)) +* **deps-dev:** bump isort from 5.10.1 to 5.11.2 ([#1782](https://github.com/awslabs/aws-lambda-powertools-python/issues/1782)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.17 to 1.26.30 ([#1785](https://github.com/awslabs/aws-lambda-powertools-python/issues/1785)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.13.post16 to 1.26.24 ([#1765](https://github.com/awslabs/aws-lambda-powertools-python/issues/1765)) +* **deps-dev:** bump aws-cdk-lib from 2.54.0 to 2.55.1 ([#1787](https://github.com/awslabs/aws-lambda-powertools-python/issues/1787)) +* **deps-dev:** bump aws-cdk-lib from 2.53.0 to 2.54.0 ([#1764](https://github.com/awslabs/aws-lambda-powertools-python/issues/1764)) +* **deps-dev:** bump flake8-bugbear from 22.10.27 to 22.12.6 ([#1760](https://github.com/awslabs/aws-lambda-powertools-python/issues/1760)) +* **deps-dev:** bump filelock from 3.8.0 to 3.8.2 ([#1759](https://github.com/awslabs/aws-lambda-powertools-python/issues/1759)) +* **deps-dev:** bump pytest-xdist from 3.0.2 to 3.1.0 ([#1758](https://github.com/awslabs/aws-lambda-powertools-python/issues/1758)) +* **deps-dev:** bump mkdocs-material from 8.5.10 to 8.5.11 ([#1756](https://github.com/awslabs/aws-lambda-powertools-python/issues/1756)) +* **deps-dev:** bump importlib-metadata from 4.13.0 to 5.1.0 ([#1750](https://github.com/awslabs/aws-lambda-powertools-python/issues/1750)) +* **deps-dev:** bump isort from 5.11.2 to 5.11.3 ([#1788](https://github.com/awslabs/aws-lambda-powertools-python/issues/1788)) +* **deps-dev:** bump flake8-black from 0.3.3 to 0.3.5 ([#1738](https://github.com/awslabs/aws-lambda-powertools-python/issues/1738)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.17 to 1.26.27 ([#1775](https://github.com/awslabs/aws-lambda-powertools-python/issues/1775)) +* **tests:** move shared_functions to unit tests + + + +## [v2.4.0] - 2022-11-24 +## Bug Fixes + +* **ci:** use gh-pages env as official docs are wrong +* **ci:** api docs path + +## Documentation + +* **idempotency:** fix register_lambda_context order ([#1747](https://github.com/awslabs/aws-lambda-powertools-python/issues/1747)) +* **streaming:** fix leftover newline + +## Features + +* **streaming:** add new s3 streaming utility ([#1719](https://github.com/awslabs/aws-lambda-powertools-python/issues/1719)) + +## Maintenance + * update v2 layer ARN on documentation -* remove leftover from fork one more time +* **ci:** re-create versioned API docs for new pages deployment +* **ci:** re-create versioned API docs for new pages deployment +* **ci:** increase permission in parent job for docs publishing +* **ci:** attempt gh-pages deployment via beta route +* **deps:** bump aws-xray-sdk from 2.10.0 to 2.11.0 ([#1730](https://github.com/awslabs/aws-lambda-powertools-python/issues/1730)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.0.post1 to 1.26.12 ([#1742](https://github.com/awslabs/aws-lambda-powertools-python/issues/1742)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.0.post1 to 1.26.11.post1 ([#1746](https://github.com/awslabs/aws-lambda-powertools-python/issues/1746)) +* **deps-dev:** bump aws-cdk-lib from 2.50.0 to 2.51.1 ([#1741](https://github.com/awslabs/aws-lambda-powertools-python/issues/1741)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.0.post1 to 1.26.13.post16 ([#1743](https://github.com/awslabs/aws-lambda-powertools-python/issues/1743)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.0.post1 to 1.26.12 ([#1744](https://github.com/awslabs/aws-lambda-powertools-python/issues/1744)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.4 to 1.26.11.post1 ([#1740](https://github.com/awslabs/aws-lambda-powertools-python/issues/1740)) +* **deps-dev:** bump types-requests from 2.28.11.4 to 2.28.11.5 ([#1729](https://github.com/awslabs/aws-lambda-powertools-python/issues/1729)) +* **deps-dev:** bump mkdocs-material from 8.5.9 to 8.5.10 ([#1731](https://github.com/awslabs/aws-lambda-powertools-python/issues/1731)) +* **governance:** remove markdown rendering from docs issue template + +## Regression + +* **ci:** new gh-pages beta doesn't work either; reverting as gh-pages is disrupted + + + +## [v2.3.1] - 2022-11-21 +## Bug Fixes + +* **apigateway:** support dynamic routes with equal sign (RFC3986) ([#1737](https://github.com/awslabs/aws-lambda-powertools-python/issues/1737)) + +## Maintenance + * update v2 layer ARN on documentation +* test build layer hardware to 8 core +* **deps-dev:** bump mypy-boto3-xray from 1.26.9 to 1.26.11.post1 ([#1734](https://github.com/awslabs/aws-lambda-powertools-python/issues/1734)) + + + +## [v2.3.0] - 2022-11-17 +## Bug Fixes + +* **apigateway:** support nested router decorators ([#1709](https://github.com/awslabs/aws-lambda-powertools-python/issues/1709)) +* **ci:** increase permission to allow version sync back to repo +* **ci:** disable pre-commit hook download from version bump +* **ci:** setup git client earlier to prevent dirty stash error +* **parameters:** get_secret correctly return SecretBinary value ([#1717](https://github.com/awslabs/aws-lambda-powertools-python/issues/1717)) + +## Documentation + +* project name consistency +* **apigateway:** add all resolvers in testing your code section for accuracy ([#1688](https://github.com/awslabs/aws-lambda-powertools-python/issues/1688)) +* **examples:** linting unnecessary whitespace +* **homepage:** update default value for `POWERTOOLS_DEV` ([#1695](https://github.com/awslabs/aws-lambda-powertools-python/issues/1695)) +* **idempotency:** add missing Lambda Context; note on thread-safe ([#1732](https://github.com/awslabs/aws-lambda-powertools-python/issues/1732)) +* **logger:** update uncaught exception message value + +## Features + +* **apigateway:** multiple exceptions in exception_handler ([#1707](https://github.com/awslabs/aws-lambda-powertools-python/issues/1707)) +* **event_sources:** extract CloudWatch Logs in Kinesis streams ([#1710](https://github.com/awslabs/aws-lambda-powertools-python/issues/1710)) +* **logger:** log uncaught exceptions via system's exception hook ([#1727](https://github.com/awslabs/aws-lambda-powertools-python/issues/1727)) +* **parser:** export Pydantic.errors through escape hatch ([#1728](https://github.com/awslabs/aws-lambda-powertools-python/issues/1728)) +* **parser:** extract CloudWatch Logs in Kinesis streams ([#1726](https://github.com/awslabs/aws-lambda-powertools-python/issues/1726)) + +## Maintenance + +* apigw test event wrongly set with base64 * update v2 layer ARN on documentation -* debug full event -* print full workflow event depth -* bump to 1.26.2 -* **batch:** deprecate sqs_batch_processor ([#1463](https://github.com/awslabs/aws-lambda-powertools-python/issues/1463)) -* **ci:** use new custom hw for E2E -* **ci:** re-create versioned API docs for new pages deployment -* **ci:** add end to end testing mechanism ([#1247](https://github.com/awslabs/aws-lambda-powertools-python/issues/1247)) -* **ci:** improve error handling for non-issue numbers -* **ci:** experiment with conditional on outputs -* **ci:** automatically add area label based on title ([#1300](https://github.com/awslabs/aws-lambda-powertools-python/issues/1300)) -* **ci:** lockdown 3rd party workflows to pin sha ([#1301](https://github.com/awslabs/aws-lambda-powertools-python/issues/1301)) -* **ci:** disable output debugging as pr body isnt accepted -* **ci:** increase release automation and limit to one manual step ([#1297](https://github.com/awslabs/aws-lambda-powertools-python/issues/1297)) -* **ci:** update project with version 1.26.4 -* **ci:** adds caching when installing python dependencies ([#1311](https://github.com/awslabs/aws-lambda-powertools-python/issues/1311)) -* **ci:** update project with version 1.26.5 -* **ci:** confirm workflow_run event -* **ci:** auto-merge cdk lib and lambda layer construct -* **ci:** move all scripts under .github/scripts -* **ci:** move error prone env to code as constants -* **ci:** make export PR reusable -* **ci:** experiment hardening origin -* **ci:** experiment hardening origin -* **ci:** test default env -* **ci:** test env expr -* **ci:** test upstream job skip -* **ci:** lockdown workflow_run by origin ([#1350](https://github.com/awslabs/aws-lambda-powertools-python/issues/1350)) -* **ci:** convert inline gh-script to file -* **ci:** fix reference error in related_issue -* **ci:** introduce codeowners ([#1352](https://github.com/awslabs/aws-lambda-powertools-python/issues/1352)) -* **ci:** use OIDC and encrypt release secrets ([#1355](https://github.com/awslabs/aws-lambda-powertools-python/issues/1355)) -* **ci:** remove core group from codeowners ([#1358](https://github.com/awslabs/aws-lambda-powertools-python/issues/1358)) -* **ci:** use gh environment for beta and prod layer deploy ([#1356](https://github.com/awslabs/aws-lambda-powertools-python/issues/1356)) -* **ci:** update project with version 1.26.6 -* **ci:** add conditional to skip pypi release ([#1366](https://github.com/awslabs/aws-lambda-powertools-python/issues/1366)) -* **ci:** update project with version 1.26.6 -* **ci:** update project with version 1.26.6 -* **ci:** increase skip_pypi logic to cover tests/changelog on re-run failures -* **ci:** remove leftover logic from on_merged_pr workflow -* **ci:** drop 3.6 from workflows -* **ci:** drop 3.6 from workflows ([#1395](https://github.com/awslabs/aws-lambda-powertools-python/issues/1395)) -* **ci:** temporarily disable changelog push on release -* **ci:** move changelog generation to rebuild_latest_doc workflow -* **ci:** update project with version -* **ci:** move changelog generation to rebuild_latest_doc workflow -* **ci:** readd changelog step on release -* **ci:** update release automated activities -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** add manual trigger for docs -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** limits concurrency for docs workflow -* **ci:** sync area labels to prevent dedup -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** reduce payload and only send prod notification -* **ci:** remove area/utilities conflicting label -* **ci:** remove conventional changelog commit to reduce noise -* **ci:** fix reference error in related_issue -* **ci:** temp disable e2e matrix -* **ci:** include py version in stack and cache lock -* **ci:** revert e2e py version matrix -* **ci:** disable e2e py version matrix due to concurrent locking -* **ci:** prevent concurrent git update in critical workflows ([#1478](https://github.com/awslabs/aws-lambda-powertools-python/issues/1478)) -* **ci:** limit E2E workflow run for source code change -* **ci:** limits concurrency for docs workflow -* **ci:** add missing description fields -* **ci:** fix invalid dependency leftover -* **ci:** remove dangling debug step -* **ci:** add linter for GitHub Actions as pre-commit hook ([#1479](https://github.com/awslabs/aws-lambda-powertools-python/issues/1479)) -* **ci:** add workflow to suggest splitting large PRs ([#1480](https://github.com/awslabs/aws-lambda-powertools-python/issues/1480)) -* **ci:** enable ci checks for v2 -* **ci:** re-create versioned API docs for new pages deployment -* **ci:** increase permission in parent job for docs publishing -* **ci:** attempt gh-pages deployment via beta route -* **ci:** reactivate on_merged_pr workflow -* **ci:** record pr details upon labeling -* **ci:** destructure assignment on comment_large_pr -* **ci:** add note for state persistence on comment_large_pr -* **ci:** format comment on comment_large_pr script -* **ci:** create reusable docs publishing workflow ([#1482](https://github.com/awslabs/aws-lambda-powertools-python/issues/1482)) -* **ci:** create docs workflow for v2 -* **ci:** create adhoc docs workflow for v2 -* **ci:** create adhoc docs workflow for v2 -* **ci:** sync package version with pypi -* **ci:** disable v2 docs -* **ci:** improve wording on closed issues action -* **ci:** deactivate on_merged_pr workflow -* **ci:** changelog pre-generation to fetch tags from origin -* **ci:** post release on tagged issues too -* **ci:** disable mergify configuration after breaking changes ([#1188](https://github.com/awslabs/aws-lambda-powertools-python/issues/1188)) -* **ci:** bump hardware for build steps +* **ci:** revert custom hw for E2E due to lack of hw * **ci:** try bigger hardware for e2e test * **ci:** uncomment test pypi, fix version bump sync -* **ci:** migrate E2E tests to CDK CLI and off Docker ([#1501](https://github.com/awslabs/aws-lambda-powertools-python/issues/1501)) -* **ci:** replace deprecated set-output commands ([#1957](https://github.com/awslabs/aws-lambda-powertools-python/issues/1957)) -* **ci:** remove v1 workflows ([#1617](https://github.com/awslabs/aws-lambda-powertools-python/issues/1617)) -* **ci:** run codeql analysis on push only -* **ci:** disable pypi test due to maintenance mode -* **ci:** fix mergify dependabot queue -* **ci:** make release process manual -* **ci:** add queue name in mergify -* **ci:** remove mergify legacy key -* **ci:** fix typo on version description -* **ci:** update mergify bot breaking change -* **ci:** safely label PR based on title -* **ci:** split latest docs workflow * **ci:** limit to src only to prevent dependabot failures -* **ci:** revert custom hw for E2E due to lack of hw +* **ci:** use new custom hw for E2E * **ci:** prevent dependabot updates to trigger E2E -* **ci:** remove unused and undeclared OS matrix env -* **common:** reusable function to extract event from models -* **core:** expose modules in the Top-level package ([#1517](https://github.com/awslabs/aws-lambda-powertools-python/issues/1517)) -* **dep:** add cfn-lint as a dev dependency; pre-commit ([#1612](https://github.com/awslabs/aws-lambda-powertools-python/issues/1612)) -* **dep:** bump pyproject to pypi sync -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.3.0 to 1.4.0 ([#1749](https://github.com/awslabs/aws-lambda-powertools-python/issues/1749)) -* **deps:** bump pydantic from 1.10.4 to 1.10.5 ([#1931](https://github.com/awslabs/aws-lambda-powertools-python/issues/1931)) -* **deps:** bump github/codeql-action from 1 to 2 ([#1154](https://github.com/awslabs/aws-lambda-powertools-python/issues/1154)) +* **ci:** bump hardware for build steps * **deps:** bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 ([#1689](https://github.com/awslabs/aws-lambda-powertools-python/issues/1689)) -* **deps:** bump email-validator from 1.1.3 to 1.2.1 ([#1199](https://github.com/awslabs/aws-lambda-powertools-python/issues/1199)) -* **deps:** bump pydantic from 1.9.0 to 1.9.1 ([#1221](https://github.com/awslabs/aws-lambda-powertools-python/issues/1221)) +* **deps-dev:** bump types-requests from 2.28.11.3 to 2.28.11.4 ([#1701](https://github.com/awslabs/aws-lambda-powertools-python/issues/1701)) +* **deps-dev:** bump mypy-boto3-s3 from 1.25.0 to 1.26.0.post1 ([#1716](https://github.com/awslabs/aws-lambda-powertools-python/issues/1716)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.25.0 to 1.26.0.post1 ([#1704](https://github.com/awslabs/aws-lambda-powertools-python/issues/1704)) +* **deps-dev:** bump mypy-boto3-xray from 1.25.0 to 1.26.0.post1 ([#1703](https://github.com/awslabs/aws-lambda-powertools-python/issues/1703)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.25.0 to 1.26.0.post1 ([#1714](https://github.com/awslabs/aws-lambda-powertools-python/issues/1714)) +* **deps-dev:** bump flake8-bugbear from 22.10.25 to 22.10.27 ([#1665](https://github.com/awslabs/aws-lambda-powertools-python/issues/1665)) +* **deps-dev:** bump mypy-boto3-lambda from 1.25.0 to 1.26.0.post1 ([#1705](https://github.com/awslabs/aws-lambda-powertools-python/issues/1705)) +* **deps-dev:** bump mypy-boto3-xray from 1.26.0.post1 to 1.26.9 ([#1720](https://github.com/awslabs/aws-lambda-powertools-python/issues/1720)) +* **deps-dev:** bump mypy-boto3-logs from 1.25.0 to 1.26.3 ([#1702](https://github.com/awslabs/aws-lambda-powertools-python/issues/1702)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.0.post1 to 1.26.4 ([#1721](https://github.com/awslabs/aws-lambda-powertools-python/issues/1721)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.25.0 to 1.26.0.post1 ([#1722](https://github.com/awslabs/aws-lambda-powertools-python/issues/1722)) +* **deps-dev:** bump pytest-asyncio from 0.20.1 to 0.20.2 ([#1723](https://github.com/awslabs/aws-lambda-powertools-python/issues/1723)) +* **deps-dev:** bump flake8-builtins from 2.0.0 to 2.0.1 ([#1715](https://github.com/awslabs/aws-lambda-powertools-python/issues/1715)) +* **deps-dev:** bump pytest-xdist from 2.5.0 to 3.0.2 ([#1655](https://github.com/awslabs/aws-lambda-powertools-python/issues/1655)) +* **deps-dev:** bump mkdocs-material from 8.5.7 to 8.5.9 ([#1697](https://github.com/awslabs/aws-lambda-powertools-python/issues/1697)) +* **deps-dev:** bump flake8-comprehensions from 3.10.0 to 3.10.1 ([#1699](https://github.com/awslabs/aws-lambda-powertools-python/issues/1699)) +* **deps-dev:** bump types-requests from 2.28.11.2 to 2.28.11.3 ([#1698](https://github.com/awslabs/aws-lambda-powertools-python/issues/1698)) +* **deps-dev:** bump pytest-benchmark from 3.4.1 to 4.0.0 ([#1659](https://github.com/awslabs/aws-lambda-powertools-python/issues/1659)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.25.0 to 1.26.0.post1 ([#1691](https://github.com/awslabs/aws-lambda-powertools-python/issues/1691)) +* **deps-dev:** bump mypy-boto3-ssm from 1.25.0 to 1.26.0.post1 ([#1690](https://github.com/awslabs/aws-lambda-powertools-python/issues/1690)) +* **logger:** uncaught exception to use exception value as message +* **logger:** overload inject_lambda_context with generics ([#1583](https://github.com/awslabs/aws-lambda-powertools-python/issues/1583)) + + + +## [v2.2.0] - 2022-11-07 +## Documentation + +* **homepage:** remove v1 layer limitation on pydantic not being included +* **tracer:** add note on why X-Ray SDK over ADOT closes [#1675](https://github.com/awslabs/aws-lambda-powertools-python/issues/1675) + +## Features + +* **metrics:** add EphemeralMetrics as a non-singleton option ([#1676](https://github.com/awslabs/aws-lambda-powertools-python/issues/1676)) +* **parameters:** add get_parameters_by_name for SSM params in distinct paths ([#1678](https://github.com/awslabs/aws-lambda-powertools-python/issues/1678)) + +## Maintenance + +* update v2 layer ARN on documentation * **deps:** bump package to 2.2.0 -* **deps:** bump actions/setup-python from 2.3.1 to 3 ([#1048](https://github.com/awslabs/aws-lambda-powertools-python/issues/1048)) -* **deps:** bump actions/checkout from 2 to 3 ([#1052](https://github.com/awslabs/aws-lambda-powertools-python/issues/1052)) -* **deps:** bump actions/github-script from 5 to 6 ([#1023](https://github.com/awslabs/aws-lambda-powertools-python/issues/1023)) -* **deps:** bump fastjsonschema from 2.15.2 to 2.15.3 ([#949](https://github.com/awslabs/aws-lambda-powertools-python/issues/949)) -* **deps:** bump actions/setup-python from 3 to 4 ([#1244](https://github.com/awslabs/aws-lambda-powertools-python/issues/1244)) -* **deps:** bump boto3 from 1.18.56 to 1.18.58 ([#755](https://github.com/awslabs/aws-lambda-powertools-python/issues/755)) -* **deps:** bump fastjsonschema from 2.16.2 to 2.16.3 ([#1961](https://github.com/awslabs/aws-lambda-powertools-python/issues/1961)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.0 to 2.1.1 ([#1958](https://github.com/awslabs/aws-lambda-powertools-python/issues/1958)) -* **deps:** bump docker/setup-buildx-action from 2.4.0 to 2.4.1 ([#1903](https://github.com/awslabs/aws-lambda-powertools-python/issues/1903)) -* **deps:** bump dependabot/fetch-metadata from 1.1.1 to 1.3.2 ([#1269](https://github.com/awslabs/aws-lambda-powertools-python/issues/1269)) -* **deps:** bump docker/setup-qemu-action from 2.0.0 to 2.1.0 ([#1627](https://github.com/awslabs/aws-lambda-powertools-python/issues/1627)) -* **deps:** bump aws-xray-sdk from 2.9.0 to 2.10.0 ([#1270](https://github.com/awslabs/aws-lambda-powertools-python/issues/1270)) +* **deps-dev:** bump aws-cdk-lib from 2.49.0 to 2.50.0 ([#1683](https://github.com/awslabs/aws-lambda-powertools-python/issues/1683)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.25.0 to 1.26.0.post1 ([#1682](https://github.com/awslabs/aws-lambda-powertools-python/issues/1682)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.25.0 to 1.26.0.post1 ([#1679](https://github.com/awslabs/aws-lambda-powertools-python/issues/1679)) +* **package:** correct pyproject version manually + + + +## [v2.1.0] - 2022-10-31 +## Bug Fixes + +* **ci:** linting issues after flake8-blackbear,mypy upgrades +* **deps:** update build system to poetry-core ([#1651](https://github.com/awslabs/aws-lambda-powertools-python/issues/1651)) +* **idempotency:** idempotent_function should support standalone falsy values ([#1669](https://github.com/awslabs/aws-lambda-powertools-python/issues/1669)) +* **logger:** fix unknown attributes being ignored by mypy ([#1670](https://github.com/awslabs/aws-lambda-powertools-python/issues/1670)) + +## Documentation + +* **community:** fix social handlers for Ran ([#1654](https://github.com/awslabs/aws-lambda-powertools-python/issues/1654)) +* **community:** fix twitch parent domain for embedded video +* **homepage:** remove 3.6 and add hero image +* **homepage:** add Pulumi code example ([#1652](https://github.com/awslabs/aws-lambda-powertools-python/issues/1652)) +* **index:** fold support us banner +* **index:** add quotes to pip for zsh customers +* **install:** address early v2 feedback on installation and project support +* **we-made-this:** new community content section ([#1650](https://github.com/awslabs/aws-lambda-powertools-python/issues/1650)) + +## Features + +* **layers:** add layer balancer script ([#1643](https://github.com/awslabs/aws-lambda-powertools-python/issues/1643)) +* **logger:** add use_rfc3339 and auto-complete formatter opts in Logger ([#1662](https://github.com/awslabs/aws-lambda-powertools-python/issues/1662)) +* **logger:** accept arbitrary keyword=value for ephemeral metadata ([#1658](https://github.com/awslabs/aws-lambda-powertools-python/issues/1658)) + +## Maintenance + +* update v2 layer ARN on documentation +* **ci:** fix typo on version description * **deps:** bump peaceiris/actions-gh-pages from 3.8.0 to 3.9.0 ([#1649](https://github.com/awslabs/aws-lambda-powertools-python/issues/1649)) -* **deps:** bump dependabot/fetch-metadata from 1.3.2 to 1.3.3 ([#1273](https://github.com/awslabs/aws-lambda-powertools-python/issues/1273)) -* **deps:** bump boto3 from 1.18.58 to 1.18.59 ([#760](https://github.com/awslabs/aws-lambda-powertools-python/issues/760)) -* **deps:** bump pydantic from 1.8.2 to 1.9.0 ([#933](https://github.com/awslabs/aws-lambda-powertools-python/issues/933)) -* **deps:** bump docker/setup-buildx-action from 2.0.0 to 2.4.0 ([#1873](https://github.com/awslabs/aws-lambda-powertools-python/issues/1873)) -* **deps:** bump actions/setup-node from 2 to 3 ([#1281](https://github.com/awslabs/aws-lambda-powertools-python/issues/1281)) -* **deps:** bump aws-cdk-lib from 2.29.0 to 2.31.1 ([#1290](https://github.com/awslabs/aws-lambda-powertools-python/issues/1290)) -* **deps:** bump cdk-lambda-powertools-python-layer ([#1284](https://github.com/awslabs/aws-lambda-powertools-python/issues/1284)) -* **deps:** bump attrs from 21.2.0 to 21.4.0 ([#1282](https://github.com/awslabs/aws-lambda-powertools-python/issues/1282)) -* **deps:** bump jsii from 1.61.0 to 1.62.0 ([#1294](https://github.com/awslabs/aws-lambda-powertools-python/issues/1294)) -* **deps:** bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 ([#1855](https://github.com/awslabs/aws-lambda-powertools-python/issues/1855)) -* **deps:** bump constructs from 10.1.1 to 10.1.46 ([#1306](https://github.com/awslabs/aws-lambda-powertools-python/issues/1306)) -* **deps:** bump codecov/codecov-action from 3.0.0 to 3.1.0 ([#1143](https://github.com/awslabs/aws-lambda-powertools-python/issues/1143)) -* **deps:** remove email-validator; use Str over EmailStr in SES model ([#1608](https://github.com/awslabs/aws-lambda-powertools-python/issues/1608)) -* **deps:** bump release-drafter/release-drafter from 5.21.0 to 5.21.1 ([#1611](https://github.com/awslabs/aws-lambda-powertools-python/issues/1611)) -* **deps:** lock importlib to 4.x -* **deps:** bump fastjsonschema from 2.15.3 to 2.16.1 ([#1309](https://github.com/awslabs/aws-lambda-powertools-python/issues/1309)) -* **deps:** bump constructs from 10.1.1 to 10.1.49 ([#1308](https://github.com/awslabs/aws-lambda-powertools-python/issues/1308)) -* **deps:** bump codecov/codecov-action from 2.1.0 to 3.0.0 ([#1102](https://github.com/awslabs/aws-lambda-powertools-python/issues/1102)) -* **deps:** bump constructs from 10.1.1 to 10.1.51 ([#1323](https://github.com/awslabs/aws-lambda-powertools-python/issues/1323)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.5 to 2.1.0 ([#1943](https://github.com/awslabs/aws-lambda-powertools-python/issues/1943)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.1 to 2.1.2 ([#1979](https://github.com/awslabs/aws-lambda-powertools-python/issues/1979)) -* **deps:** bump fastjsonschema from 2.15.1 to 2.15.2 ([#891](https://github.com/awslabs/aws-lambda-powertools-python/issues/891)) -* **deps:** bump constructs from 10.1.1 to 10.1.52 ([#1343](https://github.com/awslabs/aws-lambda-powertools-python/issues/1343)) -* **deps:** bump peaceiris/actions-gh-pages from 3.9.1 to 3.9.2 ([#1841](https://github.com/awslabs/aws-lambda-powertools-python/issues/1841)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.4 to 2.0.5 ([#1837](https://github.com/awslabs/aws-lambda-powertools-python/issues/1837)) -* **deps:** bump future from 0.18.2 to 0.18.3 ([#1836](https://github.com/awslabs/aws-lambda-powertools-python/issues/1836)) -* **deps:** bump pydantic from 1.10.5 to 1.10.6 ([#1991](https://github.com/awslabs/aws-lambda-powertools-python/issues/1991)) -* **deps:** bump actions/upload-artifact from 2 to 3 ([#1103](https://github.com/awslabs/aws-lambda-powertools-python/issues/1103)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.3 to 2.0.4 ([#1821](https://github.com/awslabs/aws-lambda-powertools-python/issues/1821)) -* **deps:** bump boto3 from 1.18.59 to 1.18.61 ([#766](https://github.com/awslabs/aws-lambda-powertools-python/issues/766)) -* **deps:** bump peaceiris/actions-gh-pages from 3.9.0 to 3.9.1 ([#1814](https://github.com/awslabs/aws-lambda-powertools-python/issues/1814)) -* **deps:** bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 ([#1565](https://github.com/awslabs/aws-lambda-powertools-python/issues/1565)) -* **deps:** bump pydantic from 1.10.2 to 1.10.4 ([#1817](https://github.com/awslabs/aws-lambda-powertools-python/issues/1817)) -* **deps:** bump aws-xray-sdk from 2.8.0 to 2.9.0 ([#876](https://github.com/awslabs/aws-lambda-powertools-python/issues/876)) -* **deps:** bump constructs from 10.1.1 to 10.1.59 ([#1396](https://github.com/awslabs/aws-lambda-powertools-python/issues/1396)) -* **deps:** bump jsii from 1.57.0 to 1.63.1 ([#1390](https://github.com/awslabs/aws-lambda-powertools-python/issues/1390)) -* **deps:** support arm64 when developing locally ([#862](https://github.com/awslabs/aws-lambda-powertools-python/issues/862)) -* **deps:** bump gitpython from 3.1.29 to 3.1.30 ([#1812](https://github.com/awslabs/aws-lambda-powertools-python/issues/1812)) -* **deps:** bump jsii from 1.57.0 to 1.63.2 ([#1400](https://github.com/awslabs/aws-lambda-powertools-python/issues/1400)) -* **deps:** bump constructs from 10.1.1 to 10.1.60 ([#1399](https://github.com/awslabs/aws-lambda-powertools-python/issues/1399)) -* **deps:** bump constructs from 10.1.1 to 10.1.63 ([#1402](https://github.com/awslabs/aws-lambda-powertools-python/issues/1402)) -* **deps:** bump attrs from 21.4.0 to 22.1.0 ([#1397](https://github.com/awslabs/aws-lambda-powertools-python/issues/1397)) -* **deps:** bump email-validator from 1.2.1 to 1.3.0 ([#1533](https://github.com/awslabs/aws-lambda-powertools-python/issues/1533)) -* **deps:** bump constructs from 10.1.1 to 10.1.64 ([#1405](https://github.com/awslabs/aws-lambda-powertools-python/issues/1405)) -* **deps:** bump fastjsonschema from 2.16.1 to 2.16.2 ([#1530](https://github.com/awslabs/aws-lambda-powertools-python/issues/1530)) -* **deps:** bump codecov/codecov-action from 3.1.0 to 3.1.1 ([#1529](https://github.com/awslabs/aws-lambda-powertools-python/issues/1529)) -* **deps:** bump actions/setup-python from 3 to 4 ([#1528](https://github.com/awslabs/aws-lambda-powertools-python/issues/1528)) -* **deps:** bump constructs from 10.1.1 to 10.1.65 ([#1407](https://github.com/awslabs/aws-lambda-powertools-python/issues/1407)) -* **deps:** bump release-drafter/release-drafter from 5.20.1 to 5.21.0 ([#1520](https://github.com/awslabs/aws-lambda-powertools-python/issues/1520)) -* **deps:** bump actions/setup-python from 2.3.0 to 2.3.1 ([#852](https://github.com/awslabs/aws-lambda-powertools-python/issues/852)) -* **deps:** bump actions/setup-python from 2.2.2 to 2.3.0 ([#831](https://github.com/awslabs/aws-lambda-powertools-python/issues/831)) -* **deps:** bump constructs from 10.1.1 to 10.1.66 ([#1414](https://github.com/awslabs/aws-lambda-powertools-python/issues/1414)) -* **deps:** bump aws-actions/configure-aws-credentials from 1 to 2 ([#1987](https://github.com/awslabs/aws-lambda-powertools-python/issues/1987)) -* **deps:** bump release-drafter/release-drafter from 5.21.1 to 5.22.0 ([#1802](https://github.com/awslabs/aws-lambda-powertools-python/issues/1802)) -* **deps:** bump boto3 from 1.18.61 to 1.19.6 ([#783](https://github.com/awslabs/aws-lambda-powertools-python/issues/783)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.1 to 2.0.3 ([#1801](https://github.com/awslabs/aws-lambda-powertools-python/issues/1801)) -* **deps:** bump aws-xray-sdk from 2.10.0 to 2.11.0 ([#1730](https://github.com/awslabs/aws-lambda-powertools-python/issues/1730)) -* **deps:** bump certifi from 2022.9.24 to 2022.12.7 ([#1768](https://github.com/awslabs/aws-lambda-powertools-python/issues/1768)) -* **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) +* **deps:** bump docker/setup-qemu-action from 2.0.0 to 2.1.0 ([#1627](https://github.com/awslabs/aws-lambda-powertools-python/issues/1627)) +* **deps-dev:** bump aws-cdk-lib from 2.47.0 to 2.48.0 ([#1664](https://github.com/awslabs/aws-lambda-powertools-python/issues/1664)) +* **deps-dev:** bump flake8-variables-names from 0.0.4 to 0.0.5 ([#1628](https://github.com/awslabs/aws-lambda-powertools-python/issues/1628)) +* **deps-dev:** bump pytest-asyncio from 0.16.0 to 0.20.1 ([#1635](https://github.com/awslabs/aws-lambda-powertools-python/issues/1635)) +* **deps-dev:** bump aws-cdk-lib from 2.48.0 to 2.49.0 ([#1671](https://github.com/awslabs/aws-lambda-powertools-python/issues/1671)) +* **docs:** remove v2 banner on top of the docs +* **governance:** remove 'area/' from PR labels + + + +## [v2.0.0] - 2022-10-24 +## Bug Fixes + +* lock dependencies +* mypy errors +* lint files +* **ci:** temporarly remove pypi test deployment +* **ci:** use docker driver on buildx +* **ci:** new artifact path, sed gnu/linux syntax, and pypi test +* **ci:** secret and OIDC inheritance in nested children workflow +* **ci:** build without buildkit +* **ci:** fix arm64 layer builds +* **ci:** remove v2 suffix from SAR apps ([#1633](https://github.com/awslabs/aws-lambda-powertools-python/issues/1633)) +* **ci:** workflow should use npx for CDK CLI +* **parser:** S3Model Object Deleted omits size and eTag attr ([#1638](https://github.com/awslabs/aws-lambda-powertools-python/issues/1638)) + +## Code Refactoring + +* **apigateway:** remove POWERTOOLS_EVENT_HANDLER_DEBUG env var ([#1620](https://github.com/awslabs/aws-lambda-powertools-python/issues/1620)) +* **batch:** remove legacy sqs_batch_processor ([#1492](https://github.com/awslabs/aws-lambda-powertools-python/issues/1492)) +* **e2e:** make table name dynamic +* **e2e:** fix idempotency typing + +## Documentation + +* **batch:** remove legacy reference to sqs processor +* **homepage:** note about v2 version +* **homepage:** auto-update Layer ARN on every release ([#1610](https://github.com/awslabs/aws-lambda-powertools-python/issues/1610)) +* **roadmap:** refresh roadmap post-v2 launch +* **roadmap:** include observability provider and lambda layer themes before v2 +* **upgrade_guide:** add latest changes and quick summary ([#1623](https://github.com/awslabs/aws-lambda-powertools-python/issues/1623)) +* **v2:** document optional dependencies and local dev ([#1574](https://github.com/awslabs/aws-lambda-powertools-python/issues/1574)) + +## Features + +* **apigateway:** ignore trailing slashes in routes (APIGatewayRestResolver) ([#1609](https://github.com/awslabs/aws-lambda-powertools-python/issues/1609)) +* **ci:** release docs as alpha when doing a pre-release ([#1624](https://github.com/awslabs/aws-lambda-powertools-python/issues/1624)) +* **data-classes:** replace AttributeValue in DynamoDBStreamEvent with deserialized Python values ([#1619](https://github.com/awslabs/aws-lambda-powertools-python/issues/1619)) +* **data_classes:** add KinesisFirehoseEvent ([#1540](https://github.com/awslabs/aws-lambda-powertools-python/issues/1540)) +* **event_handler:** improved support for headers and cookies in v2 ([#1455](https://github.com/awslabs/aws-lambda-powertools-python/issues/1455)) +* **event_handler:** add cookies as 1st class citizen in v2 ([#1487](https://github.com/awslabs/aws-lambda-powertools-python/issues/1487)) +* **idempotency:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1535](https://github.com/awslabs/aws-lambda-powertools-python/issues/1535)) +* **layer:** publish SAR v2 via Github actions ([#1585](https://github.com/awslabs/aws-lambda-powertools-python/issues/1585)) +* **layers:** add support for publishing v2 layer ([#1558](https://github.com/awslabs/aws-lambda-powertools-python/issues/1558)) +* **parameters:** migrate AppConfig to new APIs due to API deprecation ([#1553](https://github.com/awslabs/aws-lambda-powertools-python/issues/1553)) +* **tracer:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1486](https://github.com/awslabs/aws-lambda-powertools-python/issues/1486)) + +## Maintenance + +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* merge v2 branch +* bump pyproject version to 2.0 +* **ci:** make release process manual +* **ci:** migrate E2E tests to CDK CLI and off Docker ([#1501](https://github.com/awslabs/aws-lambda-powertools-python/issues/1501)) +* **ci:** remove v1 workflows ([#1617](https://github.com/awslabs/aws-lambda-powertools-python/issues/1617)) +* **core:** expose modules in the Top-level package ([#1517](https://github.com/awslabs/aws-lambda-powertools-python/issues/1517)) +* **dep:** add cfn-lint as a dev dependency; pre-commit ([#1612](https://github.com/awslabs/aws-lambda-powertools-python/issues/1612)) +* **deps:** remove email-validator; use Str over EmailStr in SES model ([#1608](https://github.com/awslabs/aws-lambda-powertools-python/issues/1608)) +* **deps:** bump release-drafter/release-drafter from 5.21.0 to 5.21.1 ([#1611](https://github.com/awslabs/aws-lambda-powertools-python/issues/1611)) +* **deps:** lock importlib to 4.x +* **deps-dev:** bump mypy-boto3-s3 from 1.24.76 to 1.24.94 ([#1622](https://github.com/awslabs/aws-lambda-powertools-python/issues/1622)) +* **deps-dev:** bump aws-cdk-lib from 2.46.0 to 2.47.0 ([#1629](https://github.com/awslabs/aws-lambda-powertools-python/issues/1629)) +* **layer:** bump to 1.31.1 (v39) + + + +## [v1.31.1] - 2022-10-14 +## Bug Fixes + +* **parser:** loose validation on SNS fields to support FIFO ([#1606](https://github.com/awslabs/aws-lambda-powertools-python/issues/1606)) + +## Documentation + +* **governance:** allow community to suggest feature content ([#1593](https://github.com/awslabs/aws-lambda-powertools-python/issues/1593)) +* **governance:** new form to allow customers self-nominate as public reference ([#1589](https://github.com/awslabs/aws-lambda-powertools-python/issues/1589)) +* **homepage:** include .NET powertools +* **idempotency:** "persisntence" typo ([#1596](https://github.com/awslabs/aws-lambda-powertools-python/issues/1596)) +* **logger:** fix typo. ([#1587](https://github.com/awslabs/aws-lambda-powertools-python/issues/1587)) + +## Maintenance + +* add dummy v2 sar deploy job +* bump layer version to 38 +* **deps-dev:** bump mypy-boto3-ssm from 1.24.81 to 1.24.90 ([#1594](https://github.com/awslabs/aws-lambda-powertools-python/issues/1594)) +* **deps-dev:** bump flake8-builtins from 1.5.3 to 2.0.0 ([#1582](https://github.com/awslabs/aws-lambda-powertools-python/issues/1582)) + + + +## [v1.31.0] - 2022-10-10 +## Bug Fixes + +* **metrics:** ensure dimension_set is reused across instances (pointer) ([#1581](https://github.com/awslabs/aws-lambda-powertools-python/issues/1581)) + +## Documentation + +* **readme:** add lambda layer latest version badge + +## Features + +* **parser:** add KinesisFirehoseModel ([#1556](https://github.com/awslabs/aws-lambda-powertools-python/issues/1556)) + +## Maintenance + +* **deps-dev:** bump types-requests from 2.28.11.1 to 2.28.11.2 ([#1576](https://github.com/awslabs/aws-lambda-powertools-python/issues/1576)) +* **deps-dev:** bump typing-extensions from 4.3.0 to 4.4.0 ([#1575](https://github.com/awslabs/aws-lambda-powertools-python/issues/1575)) +* **layer:** remove unsused GetFunction permission for the canary +* **layer:** bump to latest version 37 + + + +## [v1.30.0] - 2022-10-05 +## Bug Fixes + +* **apigateway:** update Response class to require status_code only ([#1560](https://github.com/awslabs/aws-lambda-powertools-python/issues/1560)) +* **ci:** integrate isort 5.0 with black to resolve conflicts +* **event_sources:** implement Mapping protocol on DictWrapper for better interop with existing middlewares ([#1516](https://github.com/awslabs/aws-lambda-powertools-python/issues/1516)) +* **typing:** fix mypy error +* **typing:** level arg in copy_config_to_registered_loggers ([#1534](https://github.com/awslabs/aws-lambda-powertools-python/issues/1534)) + +## Documentation + +* **batch:** document the new lambda context feature +* **homepage:** introduce POWERTOOLS_DEV env var ([#1569](https://github.com/awslabs/aws-lambda-powertools-python/issues/1569)) +* **multiple:** fix highlighting after new isort/black integration +* **parser:** add JSON string field extension example ([#1526](https://github.com/awslabs/aws-lambda-powertools-python/issues/1526)) + +## Features + +* **batch:** inject lambda_context if record handler signature accepts it ([#1561](https://github.com/awslabs/aws-lambda-powertools-python/issues/1561)) +* **event-handler:** context support to share data between routers ([#1567](https://github.com/awslabs/aws-lambda-powertools-python/issues/1567)) +* **logger:** introduce POWERTOOLS_DEBUG for internal debugging ([#1572](https://github.com/awslabs/aws-lambda-powertools-python/issues/1572)) +* **logger:** include logger name attribute when copy_config_to_registered_logger is used ([#1568](https://github.com/awslabs/aws-lambda-powertools-python/issues/1568)) +* **logger:** pretty-print JSON when POWERTOOLS_DEV is set ([#1548](https://github.com/awslabs/aws-lambda-powertools-python/issues/1548)) + +## Maintenance + +* **dep:** bump pyproject to pypi sync +* **deps:** bump fastjsonschema from 2.16.1 to 2.16.2 ([#1530](https://github.com/awslabs/aws-lambda-powertools-python/issues/1530)) +* **deps:** bump actions/setup-python from 3 to 4 ([#1528](https://github.com/awslabs/aws-lambda-powertools-python/issues/1528)) +* **deps:** bump codecov/codecov-action from 3.1.0 to 3.1.1 ([#1529](https://github.com/awslabs/aws-lambda-powertools-python/issues/1529)) +* **deps:** bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 ([#1565](https://github.com/awslabs/aws-lambda-powertools-python/issues/1565)) +* **deps:** bump email-validator from 1.2.1 to 1.3.0 ([#1533](https://github.com/awslabs/aws-lambda-powertools-python/issues/1533)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.24.54 to 1.24.83 ([#1557](https://github.com/awslabs/aws-lambda-powertools-python/issues/1557)) +* **deps-dev:** bump mkdocs-material from 8.5.3 to 8.5.4 ([#1563](https://github.com/awslabs/aws-lambda-powertools-python/issues/1563)) +* **deps-dev:** bump pytest-cov from 3.0.0 to 4.0.0 ([#1551](https://github.com/awslabs/aws-lambda-powertools-python/issues/1551)) +* **deps-dev:** bump flake8-bugbear from 22.9.11 to 22.9.23 ([#1541](https://github.com/awslabs/aws-lambda-powertools-python/issues/1541)) +* **deps-dev:** bump types-requests from 2.28.11 to 2.28.11.1 ([#1571](https://github.com/awslabs/aws-lambda-powertools-python/issues/1571)) +* **deps-dev:** bump mypy-boto3-ssm from 1.24.69 to 1.24.80 ([#1542](https://github.com/awslabs/aws-lambda-powertools-python/issues/1542)) +* **deps-dev:** bump mako from 1.2.2 to 1.2.3 ([#1537](https://github.com/awslabs/aws-lambda-powertools-python/issues/1537)) +* **deps-dev:** bump types-requests from 2.28.10 to 2.28.11 ([#1538](https://github.com/awslabs/aws-lambda-powertools-python/issues/1538)) +* **deps-dev:** bump mkdocs-material from 8.5.1 to 8.5.3 ([#1532](https://github.com/awslabs/aws-lambda-powertools-python/issues/1532)) +* **deps-dev:** bump mypy-boto3-ssm from 1.24.80 to 1.24.81 ([#1544](https://github.com/awslabs/aws-lambda-powertools-python/issues/1544)) +* **deps-dev:** bump mypy-boto3-s3 from 1.24.36.post1 to 1.24.76 ([#1531](https://github.com/awslabs/aws-lambda-powertools-python/issues/1531)) +* **docs:** bump layer version to 36 (1.29.2) +* **layers:** add dummy v2 layer automation +* **lint:** use new isort black integration +* **multiple:** localize powertools_dev env logic and warning ([#1570](https://github.com/awslabs/aws-lambda-powertools-python/issues/1570)) + + + +## [v1.29.2] - 2022-09-19 +## Bug Fixes + +* **deps:** bump dev dep mako version to address CVE-2022-40023 ([#1524](https://github.com/awslabs/aws-lambda-powertools-python/issues/1524)) + +## Maintenance + +* **deps:** bump release-drafter/release-drafter from 5.20.1 to 5.21.0 ([#1520](https://github.com/awslabs/aws-lambda-powertools-python/issues/1520)) +* **deps-dev:** bump mkdocs-material from 8.5.0 to 8.5.1 ([#1521](https://github.com/awslabs/aws-lambda-powertools-python/issues/1521)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.60 to 1.24.74 ([#1522](https://github.com/awslabs/aws-lambda-powertools-python/issues/1522)) + + + +## [v1.29.1] - 2022-09-13 + + +## [v1.29.0] - 2022-09-13 +## Bug Fixes + +* **ci:** ignore v2 action for now +* **ci:** only run e2e tests on py 3.7 +* **ci:** pass core fns to large pr workflow script +* **ci:** on_label permissioning model & workflow execution +* **ci:** ensure PR_AUTHOR is present for large_pr_split workflow +* **ci:** gracefully and successful exit changelog upon no changes +* **ci:** event resolution for on_label_added workflow +* **core:** fixes leftovers from rebase + +## Documentation + +* **layer:** upgrade to 1.28.0 (v33) + +## Features + +* **ci:** add actionlint in pre-commit hook +* **data-classes:** add KafkaEvent and KafkaEventRecord ([#1485](https://github.com/awslabs/aws-lambda-powertools-python/issues/1485)) +* **event_sources:** add CloudWatch dashboard custom widget event ([#1474](https://github.com/awslabs/aws-lambda-powertools-python/issues/1474)) +* **parser:** add KafkaMskEventModel and KafkaSelfManagedEventModel ([#1499](https://github.com/awslabs/aws-lambda-powertools-python/issues/1499)) + +## Maintenance + +* **ci:** add workflow to suggest splitting large PRs ([#1480](https://github.com/awslabs/aws-lambda-powertools-python/issues/1480)) +* **ci:** remove unused and undeclared OS matrix env +* **ci:** disable v2 docs +* **ci:** limit E2E workflow run for source code change +* **ci:** add missing description fields +* **ci:** sync package version with pypi +* **ci:** fix invalid dependency leftover +* **ci:** create adhoc docs workflow for v2 +* **ci:** create adhoc docs workflow for v2 +* **ci:** remove dangling debug step +* **ci:** create docs workflow for v2 +* **ci:** create reusable docs publishing workflow ([#1482](https://github.com/awslabs/aws-lambda-powertools-python/issues/1482)) +* **ci:** format comment on comment_large_pr script +* **ci:** add note for state persistence on comment_large_pr +* **ci:** destructure assignment on comment_large_pr +* **ci:** record pr details upon labeling +* **ci:** add linter for GitHub Actions as pre-commit hook ([#1479](https://github.com/awslabs/aws-lambda-powertools-python/issues/1479)) +* **ci:** enable ci checks for v2 +* **deps-dev:** bump black from 21.12b0 to 22.8.0 ([#1515](https://github.com/awslabs/aws-lambda-powertools-python/issues/1515)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#1481](https://github.com/awslabs/aws-lambda-powertools-python/issues/1481)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#306](https://github.com/awslabs/aws-lambda-powertools-python/issues/306)) +* **deps-dev:** bump mkdocs-material from 8.4.1 to 8.4.2 ([#1483](https://github.com/awslabs/aws-lambda-powertools-python/issues/1483)) +* **deps-dev:** revert to v1.28.0 dependencies +* **deps-dev:** bump mkdocs-material from 8.4.4 to 8.5.0 ([#1514](https://github.com/awslabs/aws-lambda-powertools-python/issues/1514)) +* **maintainers:** update release workflow link +* **maintenance:** add discord link to first PR and first issue ([#1493](https://github.com/awslabs/aws-lambda-powertools-python/issues/1493)) + + + +## [v1.28.0] - 2022-08-25 +## Bug Fixes + +* **ci:** calculate parallel jobs based on infrastructure needs ([#1475](https://github.com/awslabs/aws-lambda-powertools-python/issues/1475)) +* **ci:** del flake8 direct dep over py3.6 conflicts and docs failure +* **ci:** move from pip-tools to poetry on layers reusable workflow +* **ci:** move from pip-tools to poetry on layers to fix conflicts +* **ci:** typo and bust gh actions cache +* **ci:** use poetry to resolve layer deps; pip for CDK +* **ci:** disable poetry venv for layer workflow as cdk ignores venv +* **ci:** add cdk v2 dep for layers workflow +* **ci:** move from pip-tools to poetry on layers +* **ci:** temporarily disable changelog upon release +* **ci:** add explicit origin to fix release detached head +* **jmespath_util:** snappy as dev dep and typing example ([#1446](https://github.com/awslabs/aws-lambda-powertools-python/issues/1446)) + +## Documentation + +* **apigateway:** removes duplicate admonition ([#1426](https://github.com/awslabs/aws-lambda-powertools-python/issues/1426)) +* **home:** fix discord syntax and add Discord badge +* **home:** add discord invitation link ([#1471](https://github.com/awslabs/aws-lambda-powertools-python/issues/1471)) +* **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) +* **layer:** upgrade to 1.27.0 +* **layer:** upgrade to 1.27.0 +* **middleware-factory:** snippets split, improved, and lint ([#1451](https://github.com/awslabs/aws-lambda-powertools-python/issues/1451)) +* **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) +* **typing:** snippets split, improved, and lint ([#1465](https://github.com/awslabs/aws-lambda-powertools-python/issues/1465)) +* **validation:** snippets split, improved, and lint ([#1449](https://github.com/awslabs/aws-lambda-powertools-python/issues/1449)) + +## Features + +* **parser:** add support for Lambda Function URL ([#1442](https://github.com/awslabs/aws-lambda-powertools-python/issues/1442)) + +## Maintenance + +* **batch:** deprecate sqs_batch_processor ([#1463](https://github.com/awslabs/aws-lambda-powertools-python/issues/1463)) +* **ci:** prevent concurrent git update in critical workflows ([#1478](https://github.com/awslabs/aws-lambda-powertools-python/issues/1478)) +* **ci:** disable e2e py version matrix due to concurrent locking +* **ci:** revert e2e py version matrix +* **ci:** temp disable e2e matrix +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** reduce payload and only send prod notification +* **ci:** remove area/utilities conflicting label +* **ci:** include py version in stack and cache lock +* **ci:** remove conventional changelog commit to reduce noise +* **ci:** update changelog with latest changes * **deps:** bump release-drafter/release-drafter from 5.20.0 to 5.20.1 ([#1458](https://github.com/awslabs/aws-lambda-powertools-python/issues/1458)) -* **deps:** bump boto3 from 1.20.3 to 1.20.5 ([#817](https://github.com/awslabs/aws-lambda-powertools-python/issues/817)) -* **deps:** bump boto3 from 1.19.6 to 1.20.3 ([#809](https://github.com/awslabs/aws-lambda-powertools-python/issues/809)) -* **deps:** bump release-drafter/release-drafter from 5.22.0 to 5.23.0 ([#1947](https://github.com/awslabs/aws-lambda-powertools-python/issues/1947)) -* **deps:** bump urllib3 from 1.26.4 to 1.26.5 ([#787](https://github.com/awslabs/aws-lambda-powertools-python/issues/787)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.4.0 to 2.0.1 ([#1752](https://github.com/awslabs/aws-lambda-powertools-python/issues/1752)) -* **deps-dev:** bump mypy-boto3-logs from 1.25.0 to 1.26.3 ([#1702](https://github.com/awslabs/aws-lambda-powertools-python/issues/1702)) -* **deps-dev:** bump flake8-eradicate from 1.1.0 to 1.2.0 ([#784](https://github.com/awslabs/aws-lambda-powertools-python/issues/784)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#1481](https://github.com/awslabs/aws-lambda-powertools-python/issues/1481)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.49 to 1.26.89 ([#1996](https://github.com/awslabs/aws-lambda-powertools-python/issues/1996)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.0.post1 to 1.26.11.post1 ([#1746](https://github.com/awslabs/aws-lambda-powertools-python/issues/1746)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.0.post1 to 1.26.17 ([#1753](https://github.com/awslabs/aws-lambda-powertools-python/issues/1753)) -* **deps-dev:** bump flake8-black from 0.3.3 to 0.3.5 ([#1738](https://github.com/awslabs/aws-lambda-powertools-python/issues/1738)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.0.post1 to 1.26.12 ([#1742](https://github.com/awslabs/aws-lambda-powertools-python/issues/1742)) -* **deps-dev:** bump importlib-metadata from 4.13.0 to 5.1.0 ([#1750](https://github.com/awslabs/aws-lambda-powertools-python/issues/1750)) -* **deps-dev:** bump mkdocs-material from 8.5.10 to 8.5.11 ([#1756](https://github.com/awslabs/aws-lambda-powertools-python/issues/1756)) -* **deps-dev:** bump pytest-xdist from 3.0.2 to 3.1.0 ([#1758](https://github.com/awslabs/aws-lambda-powertools-python/issues/1758)) -* **deps-dev:** bump aws-cdk-lib from 2.50.0 to 2.51.1 ([#1741](https://github.com/awslabs/aws-lambda-powertools-python/issues/1741)) -* **deps-dev:** bump filelock from 3.8.0 to 3.8.2 ([#1759](https://github.com/awslabs/aws-lambda-powertools-python/issues/1759)) -* **deps-dev:** bump flake8-bugbear from 22.10.27 to 22.12.6 ([#1760](https://github.com/awslabs/aws-lambda-powertools-python/issues/1760)) -* **deps-dev:** bump aws-cdk-lib from 2.53.0 to 2.54.0 ([#1764](https://github.com/awslabs/aws-lambda-powertools-python/issues/1764)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.17 to 1.26.27 ([#1775](https://github.com/awslabs/aws-lambda-powertools-python/issues/1775)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.13.post16 to 1.26.24 ([#1765](https://github.com/awslabs/aws-lambda-powertools-python/issues/1765)) +* **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) * **deps-dev:** bump flake8-bugbear from 22.8.22 to 22.8.23 ([#1473](https://github.com/awslabs/aws-lambda-powertools-python/issues/1473)) -* **deps-dev:** bump flake8-isort from 4.0.0 to 4.1.1 ([#785](https://github.com/awslabs/aws-lambda-powertools-python/issues/785)) -* **deps-dev:** bump mkdocs-material from 7.3.3 to 7.3.5 ([#781](https://github.com/awslabs/aws-lambda-powertools-python/issues/781)) -* **deps-dev:** bump mkdocs-material from 7.3.5 to 7.3.6 ([#791](https://github.com/awslabs/aws-lambda-powertools-python/issues/791)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.0.post1 to 1.26.13.post16 ([#1743](https://github.com/awslabs/aws-lambda-powertools-python/issues/1743)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.0.post1 to 1.26.12 ([#1744](https://github.com/awslabs/aws-lambda-powertools-python/issues/1744)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#306](https://github.com/awslabs/aws-lambda-powertools-python/issues/306)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.4 to 1.26.11.post1 ([#1740](https://github.com/awslabs/aws-lambda-powertools-python/issues/1740)) -* **deps-dev:** bump types-requests from 2.28.11.4 to 2.28.11.5 ([#1729](https://github.com/awslabs/aws-lambda-powertools-python/issues/1729)) -* **deps-dev:** bump mkdocs-material from 8.4.1 to 8.4.2 ([#1483](https://github.com/awslabs/aws-lambda-powertools-python/issues/1483)) -* **deps-dev:** bump cfn-lint from 0.74.0 to 0.74.1 ([#1988](https://github.com/awslabs/aws-lambda-powertools-python/issues/1988)) -* **deps-dev:** bump coverage from 6.0.2 to 6.1.2 ([#810](https://github.com/awslabs/aws-lambda-powertools-python/issues/810)) * **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) -* **deps-dev:** bump pytest-asyncio from 0.20.2 to 0.20.3 ([#1767](https://github.com/awslabs/aws-lambda-powertools-python/issues/1767)) -* **deps-dev:** bump isort from 5.10.1 to 5.11.2 ([#1782](https://github.com/awslabs/aws-lambda-powertools-python/issues/1782)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.17 to 1.26.30 ([#1785](https://github.com/awslabs/aws-lambda-powertools-python/issues/1785)) -* **deps-dev:** bump aws-cdk-lib from 2.54.0 to 2.55.1 ([#1787](https://github.com/awslabs/aws-lambda-powertools-python/issues/1787)) -* **deps-dev:** bump isort from 5.11.2 to 5.11.3 ([#1788](https://github.com/awslabs/aws-lambda-powertools-python/issues/1788)) -* **deps-dev:** bump types-requests from 2.28.11.5 to 2.28.11.7 ([#1795](https://github.com/awslabs/aws-lambda-powertools-python/issues/1795)) -* **deps-dev:** revert to v1.28.0 dependencies -* **deps-dev:** bump flake8-black from 0.3.5 to 0.3.6 ([#1792](https://github.com/awslabs/aws-lambda-powertools-python/issues/1792)) -* **deps-dev:** bump black from 22.10.0 to 22.12.0 ([#1770](https://github.com/awslabs/aws-lambda-powertools-python/issues/1770)) -* **deps-dev:** bump isort from 5.9.3 to 5.10.1 ([#811](https://github.com/awslabs/aws-lambda-powertools-python/issues/811)) -* **deps-dev:** bump mkdocs-material from 8.5.9 to 8.5.10 ([#1731](https://github.com/awslabs/aws-lambda-powertools-python/issues/1731)) -* **deps-dev:** bump mkdocs-material from 8.4.4 to 8.5.0 ([#1514](https://github.com/awslabs/aws-lambda-powertools-python/issues/1514)) -* **deps-dev:** bump black from 21.12b0 to 22.8.0 ([#1515](https://github.com/awslabs/aws-lambda-powertools-python/issues/1515)) -* **deps-dev:** bump importlib-metadata from 5.1.0 to 6.0.0 ([#1804](https://github.com/awslabs/aws-lambda-powertools-python/issues/1804)) -* **deps-dev:** bump aws-cdk-lib from 2.55.1 to 2.59.0 ([#1810](https://github.com/awslabs/aws-lambda-powertools-python/issues/1810)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.60 to 1.24.74 ([#1522](https://github.com/awslabs/aws-lambda-powertools-python/issues/1522)) -* **deps-dev:** bump black from 21.10b0 to 21.11b1 ([#839](https://github.com/awslabs/aws-lambda-powertools-python/issues/839)) -* **deps-dev:** bump mkdocs-material from 8.5.0 to 8.5.1 ([#1521](https://github.com/awslabs/aws-lambda-powertools-python/issues/1521)) -* **deps-dev:** bump mypy-boto3-s3 from 1.24.36.post1 to 1.24.76 ([#1531](https://github.com/awslabs/aws-lambda-powertools-python/issues/1531)) -* **deps-dev:** bump types-requests from 2.28.6 to 2.28.7 ([#1406](https://github.com/awslabs/aws-lambda-powertools-python/issues/1406)) -* **deps-dev:** bump mkdocs-material from 8.5.1 to 8.5.3 ([#1532](https://github.com/awslabs/aws-lambda-powertools-python/issues/1532)) -* **deps-dev:** bump pytest-asyncio from 0.15.1 to 0.16.0 ([#782](https://github.com/awslabs/aws-lambda-powertools-python/issues/782)) -* **deps-dev:** bump types-requests from 2.28.10 to 2.28.11 ([#1538](https://github.com/awslabs/aws-lambda-powertools-python/issues/1538)) +* **maintainer:** add Leandro as maintainer ([#1468](https://github.com/awslabs/aws-lambda-powertools-python/issues/1468)) +* **tests:** build and deploy Lambda Layer stack once ([#1466](https://github.com/awslabs/aws-lambda-powertools-python/issues/1466)) +* **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) +* **tests:** enable end-to-end test workflow ([#1470](https://github.com/awslabs/aws-lambda-powertools-python/issues/1470)) +* **tests:** refactor E2E logger to ease maintenance, writing tests and parallelization ([#1460](https://github.com/awslabs/aws-lambda-powertools-python/issues/1460)) +* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) + +## Reverts +* fix(ci): add explicit origin to fix release detached head + + + +## [v1.27.0] - 2022-08-05 +## Bug Fixes + +* **ci:** changelog workflow must receive git tags too +* **ci:** add additional input to accurately describe intent on skip +* **ci:** job permissions +* **event_sources:** add test for Function URL AuthZ ([#1421](https://github.com/awslabs/aws-lambda-powertools-python/issues/1421)) + +## Documentation + +* **layer:** upgrade to 1.26.7 + +## Features + +* **ci:** create reusable changelog generation ([#1418](https://github.com/awslabs/aws-lambda-powertools-python/issues/1418)) +* **ci:** include changelog generation on docs build +* **ci:** create reusable changelog generation +* **event_handlers:** Add support for Lambda Function URLs ([#1408](https://github.com/awslabs/aws-lambda-powertools-python/issues/1408)) +* **metrics:** update max user-defined dimensions from 9 to 29 ([#1417](https://github.com/awslabs/aws-lambda-powertools-python/issues/1417)) + +## Maintenance + +* **ci:** sync area labels to prevent dedup +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** add manual trigger for docs +* **ci:** update changelog with latest changes +* **ci:** temporarily disable changelog push on release +* **ci:** update changelog with latest changes +* **ci:** move changelog generation to rebuild_latest_doc workflow +* **ci:** update project with version +* **ci:** update release automated activities +* **ci:** readd changelog step on release +* **ci:** move changelog generation to rebuild_latest_doc workflow +* **ci:** drop 3.6 from workflows +* **deps:** bump constructs from 10.1.1 to 10.1.60 ([#1399](https://github.com/awslabs/aws-lambda-powertools-python/issues/1399)) +* **deps:** bump constructs from 10.1.1 to 10.1.66 ([#1414](https://github.com/awslabs/aws-lambda-powertools-python/issues/1414)) +* **deps:** bump jsii from 1.57.0 to 1.63.2 ([#1400](https://github.com/awslabs/aws-lambda-powertools-python/issues/1400)) +* **deps:** bump constructs from 10.1.1 to 10.1.64 ([#1405](https://github.com/awslabs/aws-lambda-powertools-python/issues/1405)) +* **deps:** bump attrs from 21.4.0 to 22.1.0 ([#1397](https://github.com/awslabs/aws-lambda-powertools-python/issues/1397)) +* **deps:** bump constructs from 10.1.1 to 10.1.63 ([#1402](https://github.com/awslabs/aws-lambda-powertools-python/issues/1402)) +* **deps:** bump constructs from 10.1.1 to 10.1.65 ([#1407](https://github.com/awslabs/aws-lambda-powertools-python/issues/1407)) * **deps-dev:** bump types-requests from 2.28.5 to 2.28.6 ([#1401](https://github.com/awslabs/aws-lambda-powertools-python/issues/1401)) -* **deps-dev:** bump mako from 1.2.2 to 1.2.3 ([#1537](https://github.com/awslabs/aws-lambda-powertools-python/issues/1537)) -* **deps-dev:** bump mypy-boto3-ssm from 1.24.69 to 1.24.80 ([#1542](https://github.com/awslabs/aws-lambda-powertools-python/issues/1542)) -* **deps-dev:** bump isort from 5.11.3 to 5.11.4 ([#1809](https://github.com/awslabs/aws-lambda-powertools-python/issues/1809)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.12 to 1.26.40 ([#1811](https://github.com/awslabs/aws-lambda-powertools-python/issues/1811)) -* **deps-dev:** bump mypy-boto3-ssm from 1.24.80 to 1.24.81 ([#1544](https://github.com/awslabs/aws-lambda-powertools-python/issues/1544)) -* **deps-dev:** bump flake8 from 3.9.2 to 4.0.1 ([#789](https://github.com/awslabs/aws-lambda-powertools-python/issues/789)) -* **deps-dev:** bump flake8-bugbear from 22.9.11 to 22.9.23 ([#1541](https://github.com/awslabs/aws-lambda-powertools-python/issues/1541)) -* **deps-dev:** bump flake8-builtins from 2.0.1 to 2.1.0 ([#1799](https://github.com/awslabs/aws-lambda-powertools-python/issues/1799)) -* **deps-dev:** bump pytest-cov from 3.0.0 to 4.0.0 ([#1551](https://github.com/awslabs/aws-lambda-powertools-python/issues/1551)) -* **deps-dev:** bump coverage from 6.0.1 to 6.0.2 ([#764](https://github.com/awslabs/aws-lambda-powertools-python/issues/764)) +* **deps-dev:** bump types-requests from 2.28.6 to 2.28.7 ([#1406](https://github.com/awslabs/aws-lambda-powertools-python/issues/1406)) +* **docs:** remove pause sentence from roadmap ([#1409](https://github.com/awslabs/aws-lambda-powertools-python/issues/1409)) +* **docs:** update site name to test ci changelog +* **docs:** update CHANGELOG for v1.26.7 +* **docs:** update description to trigger changelog generation +* **governance:** remove devcontainer in favour of gitpod.io ([#1411](https://github.com/awslabs/aws-lambda-powertools-python/issues/1411)) +* **governance:** add pre-configured dev environment with GitPod.io to ease contributions ([#1403](https://github.com/awslabs/aws-lambda-powertools-python/issues/1403)) +* **layers:** upgrade cdk dep hashes to prevent ci fail + + + +## [v1.26.7] - 2022-07-29 +## Bug Fixes + +* **ci:** add missing oidc token generation permission +* **event_handlers:** ImportError when importing Response from top-level event_handler ([#1388](https://github.com/awslabs/aws-lambda-powertools-python/issues/1388)) + +## Documentation + +* **examples:** enforce and fix all mypy errors ([#1393](https://github.com/awslabs/aws-lambda-powertools-python/issues/1393)) + +## Features + +* **idempotency:** handle lambda timeout scenarios for INPROGRESS records ([#1387](https://github.com/awslabs/aws-lambda-powertools-python/issues/1387)) + +## Maintenance + +* **ci:** increase skip_pypi logic to cover tests/changelog on re-run failures +* **ci:** update project with version 1.26.6 +* **ci:** drop 3.6 from workflows ([#1395](https://github.com/awslabs/aws-lambda-powertools-python/issues/1395)) +* **ci:** add conditional to skip pypi release ([#1366](https://github.com/awslabs/aws-lambda-powertools-python/issues/1366)) +* **ci:** remove leftover logic from on_merged_pr workflow +* **ci:** update project with version 1.26.6 +* **ci:** update project with version 1.26.6 +* **deps:** bump jsii from 1.57.0 to 1.63.1 ([#1390](https://github.com/awslabs/aws-lambda-powertools-python/issues/1390)) +* **deps:** bump constructs from 10.1.1 to 10.1.59 ([#1396](https://github.com/awslabs/aws-lambda-powertools-python/issues/1396)) * **deps-dev:** bump flake8-isort from 4.1.1 to 4.1.2.post0 ([#1384](https://github.com/awslabs/aws-lambda-powertools-python/issues/1384)) -* **deps-dev:** bump coverage from 6.5.0 to 7.0.3 ([#1806](https://github.com/awslabs/aws-lambda-powertools-python/issues/1806)) -* **deps-dev:** bump mkdocs-material from 8.5.11 to 9.0.2 ([#1808](https://github.com/awslabs/aws-lambda-powertools-python/issues/1808)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.24.54 to 1.24.83 ([#1557](https://github.com/awslabs/aws-lambda-powertools-python/issues/1557)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.11.post1 to 1.26.35.post1 ([#1818](https://github.com/awslabs/aws-lambda-powertools-python/issues/1818)) -* **deps-dev:** bump black from 21.11b1 to 21.12b0 ([#872](https://github.com/awslabs/aws-lambda-powertools-python/issues/872)) -* **deps-dev:** bump filelock from 3.8.2 to 3.9.0 ([#1816](https://github.com/awslabs/aws-lambda-powertools-python/issues/1816)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.27 to 1.26.43 ([#1820](https://github.com/awslabs/aws-lambda-powertools-python/issues/1820)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.11.post1 to 1.26.43 ([#1819](https://github.com/awslabs/aws-lambda-powertools-python/issues/1819)) -* **deps-dev:** bump mkdocs-material from 8.5.3 to 8.5.4 ([#1563](https://github.com/awslabs/aws-lambda-powertools-python/issues/1563)) -* **deps-dev:** bump types-requests from 2.28.11 to 2.28.11.1 ([#1571](https://github.com/awslabs/aws-lambda-powertools-python/issues/1571)) -* **deps-dev:** bump ijson from 3.1.4 to 3.2.0.post0 ([#1815](https://github.com/awslabs/aws-lambda-powertools-python/issues/1815)) -* **deps-dev:** bump aws-cdk-lib from 2.67.0 to 2.68.0 ([#1992](https://github.com/awslabs/aws-lambda-powertools-python/issues/1992)) -* **deps-dev:** bump coverage from 7.0.3 to 7.0.4 ([#1822](https://github.com/awslabs/aws-lambda-powertools-python/issues/1822)) -* **deps-dev:** bump mkdocs-material from 9.0.2 to 9.0.3 ([#1823](https://github.com/awslabs/aws-lambda-powertools-python/issues/1823)) -* **deps-dev:** bump coverage from 7.0.4 to 7.0.5 ([#1829](https://github.com/awslabs/aws-lambda-powertools-python/issues/1829)) -* **deps-dev:** bump aws-cdk-lib from 2.59.0 to 2.60.0 ([#1831](https://github.com/awslabs/aws-lambda-powertools-python/issues/1831)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.18 to 1.26.49 ([#1832](https://github.com/awslabs/aws-lambda-powertools-python/issues/1832)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.40 to 1.26.49 ([#1835](https://github.com/awslabs/aws-lambda-powertools-python/issues/1835)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.43 to 1.26.49 ([#1834](https://github.com/awslabs/aws-lambda-powertools-python/issues/1834)) -* **deps-dev:** bump mkdocs-material from 9.0.3 to 9.0.4 ([#1833](https://github.com/awslabs/aws-lambda-powertools-python/issues/1833)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.84 to 1.26.87 ([#1993](https://github.com/awslabs/aws-lambda-powertools-python/issues/1993)) -* **deps-dev:** bump typing-extensions from 4.3.0 to 4.4.0 ([#1575](https://github.com/awslabs/aws-lambda-powertools-python/issues/1575)) -* **deps-dev:** bump mkdocs-material from 9.0.4 to 9.0.5 ([#1840](https://github.com/awslabs/aws-lambda-powertools-python/issues/1840)) -* **deps-dev:** bump pytest from 7.2.0 to 7.2.1 ([#1838](https://github.com/awslabs/aws-lambda-powertools-python/issues/1838)) -* **deps-dev:** bump types-requests from 2.28.11.1 to 2.28.11.2 ([#1576](https://github.com/awslabs/aws-lambda-powertools-python/issues/1576)) -* **deps-dev:** bump types-requests from 2.28.11.7 to 2.28.11.8 ([#1843](https://github.com/awslabs/aws-lambda-powertools-python/issues/1843)) -* **deps-dev:** bump mypy-boto3-xray from 1.26.9 to 1.26.11.post1 ([#1734](https://github.com/awslabs/aws-lambda-powertools-python/issues/1734)) +* **layers:** bump to 1.26.6 using layer v26 +* **maintainers:** add Ruben as a maintainer ([#1392](https://github.com/awslabs/aws-lambda-powertools-python/issues/1392)) + + + +## [v1.26.6] - 2022-07-25 +## Bug Fixes + +* **ci:** remove unsupported env in workflow_call +* **ci:** allow inherit secrets for reusable workflow +* **ci:** remove unused secret +* **ci:** label_related_issue unresolved var from history mixup +* **ci:** cond doesnt support two expr w/ env +* **ci:** only event is resolved in cond +* **ci:** unexpected symbol due to double quotes... +* **event_handlers:** handle lack of headers when using auto-compression feature ([#1325](https://github.com/awslabs/aws-lambda-powertools-python/issues/1325)) + +## Maintenance + +* dummy for PR test +* print full event depth +* print full workflow event depth +* debug full event +* remove leftover from fork one more time +* **ci:** test env expr +* **ci:** test upstream job skip +* **ci:** lockdown workflow_run by origin ([#1350](https://github.com/awslabs/aws-lambda-powertools-python/issues/1350)) +* **ci:** test default env +* **ci:** experiment hardening origin +* **ci:** experiment hardening origin +* **ci:** introduce codeowners ([#1352](https://github.com/awslabs/aws-lambda-powertools-python/issues/1352)) +* **ci:** use OIDC and encrypt release secrets ([#1355](https://github.com/awslabs/aws-lambda-powertools-python/issues/1355)) +* **ci:** remove core group from codeowners ([#1358](https://github.com/awslabs/aws-lambda-powertools-python/issues/1358)) +* **ci:** confirm workflow_run event +* **ci:** use gh environment for beta and prod layer deploy ([#1356](https://github.com/awslabs/aws-lambda-powertools-python/issues/1356)) +* **ci:** update project with version 1.26.5 +* **deps:** bump constructs from 10.1.1 to 10.1.52 ([#1343](https://github.com/awslabs/aws-lambda-powertools-python/issues/1343)) * **deps-dev:** bump mypy-boto3-cloudwatch from 1.24.0 to 1.24.35 ([#1342](https://github.com/awslabs/aws-lambda-powertools-python/issues/1342)) -* **deps-dev:** bump pytest from 7.2.1 to 7.2.2 ([#1980](https://github.com/awslabs/aws-lambda-powertools-python/issues/1980)) -* **deps-dev:** bump mkdocs-material from 7.3.2 to 7.3.3 ([#758](https://github.com/awslabs/aws-lambda-powertools-python/issues/758)) -* **deps-dev:** bump mypy from 0.910 to 0.920 ([#903](https://github.com/awslabs/aws-lambda-powertools-python/issues/903)) -* **deps-dev:** bump flake8-builtins from 1.5.3 to 2.0.0 ([#1582](https://github.com/awslabs/aws-lambda-powertools-python/issues/1582)) -* **deps-dev:** bump mypy-boto3-ssm from 1.24.81 to 1.24.90 ([#1594](https://github.com/awslabs/aws-lambda-powertools-python/issues/1594)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.9 to 2.8.19.10 ([#1973](https://github.com/awslabs/aws-lambda-powertools-python/issues/1973)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.30 to 1.26.52 ([#1847](https://github.com/awslabs/aws-lambda-powertools-python/issues/1847)) -* **deps-dev:** bump flake8-comprehensions from 3.6.1 to 3.7.0 ([#759](https://github.com/awslabs/aws-lambda-powertools-python/issues/759)) +* **governance:** update wording tech debt to summary in maintenance template +* **governance:** add new maintenance issue template for tech debt ([#1326](https://github.com/awslabs/aws-lambda-powertools-python/issues/1326)) +* **layers:** layer canary stack should not hardcode resource name +* **layers:** replace layers account secret ([#1329](https://github.com/awslabs/aws-lambda-powertools-python/issues/1329)) +* **layers:** expand to all aws commercial regions ([#1324](https://github.com/awslabs/aws-lambda-powertools-python/issues/1324)) +* **layers:** bump to 1.26.5 + +## Pull Requests + +* Merge pull request [#285](https://github.com/awslabs/aws-lambda-powertools-python/issues/285) from heitorlessa/chore/skip-dep-workflow +* Merge pull request [#284](https://github.com/awslabs/aws-lambda-powertools-python/issues/284) from heitorlessa/chore/dummy + + + +## [v1.26.5] - 2022-07-20 +## Bug Fixes + +* mathc the name of the cdk synth from the build phase +* typo in input for layer workflow +* no need to cache npm since we only install cdk cli and don't have .lock files +* add entire ARN role instead of account and role name +* path to artefact +* unzip the right artifact name +* download artefact into the layer dir +* sight, yes a whitespace character breaks the build +* **ci:** checkout project before validating related issue workflow +* **ci:** install poetry before calling setup/python with cache ([#1315](https://github.com/awslabs/aws-lambda-powertools-python/issues/1315)) +* **ci:** remove additional quotes in PR action ([#1317](https://github.com/awslabs/aws-lambda-powertools-python/issues/1317)) +* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) +* **ci:** fetch all git info so we can check tags +* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/awslabs/aws-lambda-powertools-python/issues/1316)) +* **ci:** keep layer version permission ([#1318](https://github.com/awslabs/aws-lambda-powertools-python/issues/1318)) +* **ci:** regex to catch combination of related issues workflow +* **deps:** correct mypy types as dev dependency ([#1322](https://github.com/awslabs/aws-lambda-powertools-python/issues/1322)) +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) + +## Documentation + +* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) +* **governance:** typos on PR template fixes [#1314](https://github.com/awslabs/aws-lambda-powertools-python/issues/1314) +* **governance:** add security doc to the root + +## Maintenance + +* **ci:** limits concurrency for docs workflow +* **ci:** adds caching when installing python dependencies ([#1311](https://github.com/awslabs/aws-lambda-powertools-python/issues/1311)) +* **ci:** update project with version 1.26.4 +* **ci:** fix reference error in related_issue +* **deps:** bump constructs from 10.1.1 to 10.1.51 ([#1323](https://github.com/awslabs/aws-lambda-powertools-python/issues/1323)) * **deps-dev:** bump mypy from 0.961 to 0.971 ([#1320](https://github.com/awslabs/aws-lambda-powertools-python/issues/1320)) -* **deps-dev:** bump mypy from 0.920 to 0.930 ([#925](https://github.com/awslabs/aws-lambda-powertools-python/issues/925)) -* **deps-dev:** bump aws-cdk-lib from 2.60.0 to 2.61.1 ([#1849](https://github.com/awslabs/aws-lambda-powertools-python/issues/1849)) -* **deps-dev:** bump mypy-boto3-logs from 1.26.49 to 1.26.53 ([#1850](https://github.com/awslabs/aws-lambda-powertools-python/issues/1850)) -* **deps-dev:** bump mkdocs-material from 9.0.5 to 9.0.6 ([#1851](https://github.com/awslabs/aws-lambda-powertools-python/issues/1851)) -* **deps-dev:** bump flake8-builtins from 2.0.0 to 2.0.1 ([#1715](https://github.com/awslabs/aws-lambda-powertools-python/issues/1715)) -* **deps-dev:** bump pytest-asyncio from 0.20.1 to 0.20.2 ([#1723](https://github.com/awslabs/aws-lambda-powertools-python/issues/1723)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.25.0 to 1.26.0.post1 ([#1722](https://github.com/awslabs/aws-lambda-powertools-python/issues/1722)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.49 to 1.26.55 ([#1856](https://github.com/awslabs/aws-lambda-powertools-python/issues/1856)) -* **deps-dev:** bump mypy-boto3-s3 from 1.24.76 to 1.24.94 ([#1622](https://github.com/awslabs/aws-lambda-powertools-python/issues/1622)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.0.post1 to 1.26.4 ([#1721](https://github.com/awslabs/aws-lambda-powertools-python/issues/1721)) -* **deps-dev:** bump aws-cdk-lib from 2.61.1 to 2.62.0 ([#1863](https://github.com/awslabs/aws-lambda-powertools-python/issues/1863)) -* **deps-dev:** bump coverage from 7.0.5 to 7.1.0 ([#1862](https://github.com/awslabs/aws-lambda-powertools-python/issues/1862)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.35.post1 to 1.26.57 ([#1865](https://github.com/awslabs/aws-lambda-powertools-python/issues/1865)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.24.0 to 1.24.29 ([#1295](https://github.com/awslabs/aws-lambda-powertools-python/issues/1295)) +* **governance:** fix typo on semantic commit link introduced in [#1](https://github.com/awslabs/aws-lambda-powertools-python/issues/1)aef4 +* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) +* **layers:** bump to 22 for 1.26.3 + + + +## [v1.26.4] - 2022-07-18 +## Bug Fixes + +* **ci:** checkout project before validating related issue workflow +* **ci:** fixes typos and small issues on github scripts ([#1302](https://github.com/awslabs/aws-lambda-powertools-python/issues/1302)) +* **ci:** address conditional type on_merge +* **ci:** address pr title semantic not found logic +* **ci:** address gh-actions additional quotes; remove debug +* **ci:** regex group name for on_merge workflow +* **ci:** escape outputs as certain PRs can break GH Actions expressions +* **ci:** move conditionals from yaml to code; leftover +* **ci:** move conditionals from yaml to code +* **ci:** accept core arg in label related issue workflow +* **ci:** match the name of the cdk synth from the build phase +* **ci:** regex to catch combination of related issues workflow +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) +* **parser:** raise ValidationError when SNS->SQS keys are intentionally missing ([#1299](https://github.com/awslabs/aws-lambda-powertools-python/issues/1299)) + +## Documentation + +* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/awslabs/aws-lambda-powertools-python/issues/1279)) +* **graphql:** snippets split, improved, and lint ([#1287](https://github.com/awslabs/aws-lambda-powertools-python/issues/1287)) +* **homepage:** emphasize additional powertools languages ([#1292](https://github.com/awslabs/aws-lambda-powertools-python/issues/1292)) +* **metrics:** snippets split, improved, and lint + +## Maintenance + +* **ci:** increase release automation and limit to one manual step ([#1297](https://github.com/awslabs/aws-lambda-powertools-python/issues/1297)) +* **ci:** make export PR reusable +* **ci:** auto-merge cdk lib and lambda layer construct +* **ci:** convert inline gh-script to file +* **ci:** lockdown 3rd party workflows to pin sha ([#1301](https://github.com/awslabs/aws-lambda-powertools-python/issues/1301)) +* **ci:** automatically add area label based on title ([#1300](https://github.com/awslabs/aws-lambda-powertools-python/issues/1300)) +* **ci:** disable output debugging as pr body isnt accepted +* **ci:** experiment with conditional on outputs +* **ci:** improve error handling for non-issue numbers +* **ci:** add end to end testing mechanism ([#1247](https://github.com/awslabs/aws-lambda-powertools-python/issues/1247)) +* **ci:** limits concurrency for docs workflow +* **ci:** fix reference error in related_issue +* **ci:** move error prone env to code as constants +* **ci:** move all scripts under .github/scripts +* **deps:** bump cdk-lambda-powertools-python-layer ([#1284](https://github.com/awslabs/aws-lambda-powertools-python/issues/1284)) +* **deps:** bump jsii from 1.61.0 to 1.62.0 ([#1294](https://github.com/awslabs/aws-lambda-powertools-python/issues/1294)) +* **deps:** bump constructs from 10.1.1 to 10.1.46 ([#1306](https://github.com/awslabs/aws-lambda-powertools-python/issues/1306)) +* **deps:** bump actions/setup-node from 2 to 3 ([#1281](https://github.com/awslabs/aws-lambda-powertools-python/issues/1281)) +* **deps:** bump fastjsonschema from 2.15.3 to 2.16.1 ([#1309](https://github.com/awslabs/aws-lambda-powertools-python/issues/1309)) +* **deps:** bump constructs from 10.1.1 to 10.1.49 ([#1308](https://github.com/awslabs/aws-lambda-powertools-python/issues/1308)) +* **deps:** bump attrs from 21.2.0 to 21.4.0 ([#1282](https://github.com/awslabs/aws-lambda-powertools-python/issues/1282)) +* **deps:** bump aws-cdk-lib from 2.29.0 to 2.31.1 ([#1290](https://github.com/awslabs/aws-lambda-powertools-python/issues/1290)) * **deps-dev:** bump mypy-boto3-dynamodb from 1.24.12 to 1.24.27 ([#1293](https://github.com/awslabs/aws-lambda-powertools-python/issues/1293)) -* **deps-dev:** bump aws-cdk-lib from 2.46.0 to 2.47.0 ([#1629](https://github.com/awslabs/aws-lambda-powertools-python/issues/1629)) -* **deps-dev:** bump mypy-boto3-xray from 1.26.0.post1 to 1.26.9 ([#1720](https://github.com/awslabs/aws-lambda-powertools-python/issues/1720)) -* **deps-dev:** bump mypy-boto3-lambda from 1.25.0 to 1.26.0.post1 ([#1705](https://github.com/awslabs/aws-lambda-powertools-python/issues/1705)) -* **deps-dev:** bump mypy-boto3-s3 from 1.26.0.post1 to 1.26.58 ([#1868](https://github.com/awslabs/aws-lambda-powertools-python/issues/1868)) -* **deps-dev:** bump hvac from 1.0.2 to 1.1.0 ([#1983](https://github.com/awslabs/aws-lambda-powertools-python/issues/1983)) -* **deps-dev:** bump mypy-boto3-s3 from 1.25.0 to 1.26.0.post1 ([#1716](https://github.com/awslabs/aws-lambda-powertools-python/issues/1716)) -* **deps-dev:** bump aws-cdk-lib from 2.62.1 to 2.62.2 ([#1869](https://github.com/awslabs/aws-lambda-powertools-python/issues/1869)) -* **deps-dev:** bump mkdocs-material from 9.1.0 to 9.1.1 ([#1984](https://github.com/awslabs/aws-lambda-powertools-python/issues/1984)) -* **deps-dev:** bump isort from 5.11.4 to 5.11.5 ([#1875](https://github.com/awslabs/aws-lambda-powertools-python/issues/1875)) -* **deps-dev:** bump mypy-boto3-appconfigdata from 1.25.0 to 1.26.0.post1 ([#1704](https://github.com/awslabs/aws-lambda-powertools-python/issues/1704)) -* **deps-dev:** bump mkdocs-material from 9.0.6 to 9.0.8 ([#1874](https://github.com/awslabs/aws-lambda-powertools-python/issues/1874)) -* **deps-dev:** bump flake8-bugbear from 22.12.6 to 23.1.20 ([#1854](https://github.com/awslabs/aws-lambda-powertools-python/issues/1854)) -* **deps-dev:** bump aws-cdk-lib from 2.62.2 to 2.63.0 ([#1887](https://github.com/awslabs/aws-lambda-powertools-python/issues/1887)) -* **deps-dev:** bump mypy from 0.930 to 0.931 ([#941](https://github.com/awslabs/aws-lambda-powertools-python/issues/941)) -* **deps-dev:** bump black from 22.12.0 to 23.1.0 ([#1886](https://github.com/awslabs/aws-lambda-powertools-python/issues/1886)) -* **deps-dev:** bump mypy-boto3-xray from 1.25.0 to 1.26.0.post1 ([#1703](https://github.com/awslabs/aws-lambda-powertools-python/issues/1703)) -* **deps-dev:** bump mypy-boto3-s3 from 1.26.58 to 1.26.62 ([#1889](https://github.com/awslabs/aws-lambda-powertools-python/issues/1889)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.24 to 1.26.84 ([#1981](https://github.com/awslabs/aws-lambda-powertools-python/issues/1981)) -* **deps-dev:** bump mkdocs-material from 9.0.9 to 9.0.10 ([#1888](https://github.com/awslabs/aws-lambda-powertools-python/issues/1888)) -* **deps-dev:** bump mkdocs-material from 9.0.15 to 9.1.0 ([#1976](https://github.com/awslabs/aws-lambda-powertools-python/issues/1976)) -* **deps-dev:** bump cfn-lint from 0.67.0 to 0.74.0 ([#1974](https://github.com/awslabs/aws-lambda-powertools-python/issues/1974)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.26.0.post1 to 1.26.63 ([#1895](https://github.com/awslabs/aws-lambda-powertools-python/issues/1895)) -* **deps-dev:** bump mypy-boto3-cloudwatch from 1.25.0 to 1.26.0.post1 ([#1714](https://github.com/awslabs/aws-lambda-powertools-python/issues/1714)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.24.0 to 1.24.29 ([#1295](https://github.com/awslabs/aws-lambda-powertools-python/issues/1295)) +* **governance:** remove any step relying on master branch +* **governance:** update emeritus affiliation +* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) +* **layers:** bump to 22 for 1.26.3 + + + +## [v1.26.3] - 2022-07-04 +## Bug Fixes + +* **ci:** remove utf-8 body in octokit body req +* **ci:** improve msg visibility on closed issues +* **ci:** disable merged_pr workflow +* **ci:** merged_pr add issues write access +* **ci:** quote prBody GH expr on_opened_pr +* **ci:** reusable workflow secrets param +* **logger:** support additional args for handlers when injecting lambda context ([#1276](https://github.com/awslabs/aws-lambda-powertools-python/issues/1276)) +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/awslabs/aws-lambda-powertools-python/issues/1264)) + +## Documentation + +* **lint:** add markdownlint rules and automation ([#1256](https://github.com/awslabs/aws-lambda-powertools-python/issues/1256)) +* **logger:** document enriching logs with logrecord attributes ([#1271](https://github.com/awslabs/aws-lambda-powertools-python/issues/1271)) +* **logger:** snippets split, improved, and lint ([#1262](https://github.com/awslabs/aws-lambda-powertools-python/issues/1262)) +* **metrics:** snippets split, improved, and lint ([#1272](https://github.com/awslabs/aws-lambda-powertools-python/issues/1272)) +* **tracer:** snippets split, improved, and lint ([#1261](https://github.com/awslabs/aws-lambda-powertools-python/issues/1261)) +* **tracer:** split and lint code snippets ([#1260](https://github.com/awslabs/aws-lambda-powertools-python/issues/1260)) + +## Maintenance + +* move to approach B for multiple IaC +* add sam build gitignore +* bump to version 1.26.3 +* **ci:** reactivate on_merged_pr workflow +* **ci:** improve wording on closed issues action +* **ci:** deactivate on_merged_pr workflow +* **deps:** bump aws-xray-sdk from 2.9.0 to 2.10.0 ([#1270](https://github.com/awslabs/aws-lambda-powertools-python/issues/1270)) +* **deps:** bump dependabot/fetch-metadata from 1.1.1 to 1.3.2 ([#1269](https://github.com/awslabs/aws-lambda-powertools-python/issues/1269)) +* **deps:** bump dependabot/fetch-metadata from 1.3.2 to 1.3.3 ([#1273](https://github.com/awslabs/aws-lambda-powertools-python/issues/1273)) * **deps-dev:** bump flake8-bugbear from 22.6.22 to 22.7.1 ([#1274](https://github.com/awslabs/aws-lambda-powertools-python/issues/1274)) -* **deps-dev:** bump pytest-asyncio from 0.16.0 to 0.20.1 ([#1635](https://github.com/awslabs/aws-lambda-powertools-python/issues/1635)) -* **deps-dev:** bump flake8-variables-names from 0.0.4 to 0.0.5 ([#1628](https://github.com/awslabs/aws-lambda-powertools-python/issues/1628)) -* **deps-dev:** bump aws-cdk-lib from 2.66.1 to 2.67.0 ([#1977](https://github.com/awslabs/aws-lambda-powertools-python/issues/1977)) -* **deps-dev:** bump aws-cdk-lib from 2.63.0 to 2.63.2 ([#1904](https://github.com/awslabs/aws-lambda-powertools-python/issues/1904)) -* **deps-dev:** bump aws-cdk-lib from 2.47.0 to 2.48.0 ([#1664](https://github.com/awslabs/aws-lambda-powertools-python/issues/1664)) -* **deps-dev:** bump pytest-xdist from 3.1.0 to 3.2.0 ([#1905](https://github.com/awslabs/aws-lambda-powertools-python/issues/1905)) * **deps-dev:** bump flake8-bugbear from 22.4.25 to 22.6.22 ([#1258](https://github.com/awslabs/aws-lambda-powertools-python/issues/1258)) * **deps-dev:** bump mypy-boto3-dynamodb from 1.24.0 to 1.24.12 ([#1255](https://github.com/awslabs/aws-lambda-powertools-python/issues/1255)) * **deps-dev:** bump mypy-boto3-secretsmanager ([#1252](https://github.com/awslabs/aws-lambda-powertools-python/issues/1252)) -* **deps-dev:** bump aws-cdk-lib from 2.62.0 to 2.62.1 ([#1866](https://github.com/awslabs/aws-lambda-powertools-python/issues/1866)) -* **deps-dev:** bump mypy-boto3-lambda from 1.26.55 to 1.26.80 ([#1967](https://github.com/awslabs/aws-lambda-powertools-python/issues/1967)) -* **deps-dev:** bump aws-cdk-lib from 2.48.0 to 2.49.0 ([#1671](https://github.com/awslabs/aws-lambda-powertools-python/issues/1671)) -* **deps-dev:** bump types-requests from 2.28.11.8 to 2.28.11.12 ([#1906](https://github.com/awslabs/aws-lambda-powertools-python/issues/1906)) -* **deps-dev:** bump mkdocs-material from 9.0.14 to 9.0.15 ([#1959](https://github.com/awslabs/aws-lambda-powertools-python/issues/1959)) -* **deps-dev:** bump flake8-bugbear from 21.11.29 to 22.1.11 ([#955](https://github.com/awslabs/aws-lambda-powertools-python/issues/955)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.8 to 2.8.19.9 ([#1960](https://github.com/awslabs/aws-lambda-powertools-python/issues/1960)) -* **deps-dev:** bump coverage from 6.0 to 6.0.1 ([#751](https://github.com/awslabs/aws-lambda-powertools-python/issues/751)) -* **deps-dev:** bump coverage from 7.2.0 to 7.2.1 ([#1963](https://github.com/awslabs/aws-lambda-powertools-python/issues/1963)) -* **deps-dev:** bump types-requests from 2.28.11.14 to 2.28.11.15 ([#1962](https://github.com/awslabs/aws-lambda-powertools-python/issues/1962)) -* **deps-dev:** bump mkdocs-material from 9.1.1 to 9.1.2 ([#1994](https://github.com/awslabs/aws-lambda-powertools-python/issues/1994)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.23.0.post1 to 1.24.0 ([#1233](https://github.com/awslabs/aws-lambda-powertools-python/issues/1233)) -* **deps-dev:** bump aws-cdk-lib from 2.66.0 to 2.66.1 ([#1954](https://github.com/awslabs/aws-lambda-powertools-python/issues/1954)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.23.0.post1 to 1.24.0 ([#1234](https://github.com/awslabs/aws-lambda-powertools-python/issues/1234)) -* **deps-dev:** bump mypy-boto3-cloudformation from 1.25.0 to 1.26.0.post1 ([#1679](https://github.com/awslabs/aws-lambda-powertools-python/issues/1679)) -* **deps-dev:** bump mypy-boto3-dynamodb from 1.25.0 to 1.26.0.post1 ([#1682](https://github.com/awslabs/aws-lambda-powertools-python/issues/1682)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.8 to 1.24.0 ([#1232](https://github.com/awslabs/aws-lambda-powertools-python/issues/1232)) -* **deps-dev:** bump aws-cdk-lib from 2.49.0 to 2.50.0 ([#1683](https://github.com/awslabs/aws-lambda-powertools-python/issues/1683)) -* **deps-dev:** bump mypy-boto3-ssm from 1.23.0.post1 to 1.24.0 ([#1231](https://github.com/awslabs/aws-lambda-powertools-python/issues/1231)) +* **governance:** fix on_merged_pr workflow syntax +* **governance:** warn message on closed issues +* **layers:** bump to 21 for 1.26.2 +* **test-perf:** use pytest-benchmark to improve reliability ([#1250](https://github.com/awslabs/aws-lambda-powertools-python/issues/1250)) + + + +## [v1.26.2] - 2022-06-16 +## Bug Fixes + +* **event-handler:** body to empty string in CORS preflight (ALB non-compliant) ([#1249](https://github.com/awslabs/aws-lambda-powertools-python/issues/1249)) + +## Code Refactoring + +* rename to clear_state +* rename to remove_custom_keys + +## Documentation + +* fix anchor + +## Features + +* **logger:** add option to clear state per invocation + +## Maintenance + +* bump to 1.26.2 +* **deps:** bump actions/setup-python from 3 to 4 ([#1244](https://github.com/awslabs/aws-lambda-powertools-python/issues/1244)) * **deps-dev:** bump mypy from 0.960 to 0.961 ([#1241](https://github.com/awslabs/aws-lambda-powertools-python/issues/1241)) -* **deps-dev:** bump types-requests from 2.28.11.3 to 2.28.11.4 ([#1701](https://github.com/awslabs/aws-lambda-powertools-python/issues/1701)) -* **deps-dev:** bump mkdocs-material from 8.1.9 to 8.2.4 ([#1054](https://github.com/awslabs/aws-lambda-powertools-python/issues/1054)) -* **deps-dev:** bump coverage from 7.1.0 to 7.2.0 ([#1951](https://github.com/awslabs/aws-lambda-powertools-python/issues/1951)) -* **deps-dev:** bump mkdocs-material from 9.0.13 to 9.0.14 ([#1952](https://github.com/awslabs/aws-lambda-powertools-python/issues/1952)) +* **deps-dev:** bump mypy-boto3-ssm from 1.23.0.post1 to 1.24.0 ([#1231](https://github.com/awslabs/aws-lambda-powertools-python/issues/1231)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.8 to 1.24.0 ([#1232](https://github.com/awslabs/aws-lambda-powertools-python/issues/1232)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.23.0.post1 to 1.24.0 ([#1234](https://github.com/awslabs/aws-lambda-powertools-python/issues/1234)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.23.0.post1 to 1.24.0 ([#1233](https://github.com/awslabs/aws-lambda-powertools-python/issues/1233)) +* **governance:** auto-merge on all PR events +* **governance:** add release label on pr merge +* **governance:** enforce safe scope on pr merge labelling +* **governance:** limit build workflow to code changes only +* **governance:** auto-merge workflow_dispatch off +* **governance:** auto-merge to use squash +* **governance:** check for related issue in new PRs +* **governance:** auto-merge mypy-stub dependabot +* **governance:** address gh reusable workflow limitation +* **governance:** fix workflow action requirements & syntax +* **governance:** warn message on closed issues +* **metrics:** revert dimensions test before splitting ([#1243](https://github.com/awslabs/aws-lambda-powertools-python/issues/1243)) + + + +## [v1.26.1] - 2022-06-07 +## Bug Fixes + +* **metrics:** raise SchemaValidationError for >8 metric dimensions ([#1240](https://github.com/awslabs/aws-lambda-powertools-python/issues/1240)) + +## Documentation + +* **governance:** link roadmap and maintainers doc +* **maintainers:** initial maintainers playbook ([#1222](https://github.com/awslabs/aws-lambda-powertools-python/issues/1222)) +* **roadmap:** use pinned pause issue instead + +## Maintenance + +* bump version 1.26.1 * **deps-dev:** bump mypy from 0.950 to 0.960 ([#1224](https://github.com/awslabs/aws-lambda-powertools-python/issues/1224)) * **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.0.post1 to 1.23.8 ([#1225](https://github.com/awslabs/aws-lambda-powertools-python/issues/1225)) -* **deps-dev:** bump aws-cdk-lib from 2.63.2 to 2.64.0 ([#1918](https://github.com/awslabs/aws-lambda-powertools-python/issues/1918)) + + + +## [v1.26.0] - 2022-05-20 +## Bug Fixes + +* **batch:** missing space in BatchProcessingError message ([#1201](https://github.com/awslabs/aws-lambda-powertools-python/issues/1201)) +* **batch:** docstring fix for success_handler() record parameter ([#1202](https://github.com/awslabs/aws-lambda-powertools-python/issues/1202)) +* **docs:** remove Slack link ([#1210](https://github.com/awslabs/aws-lambda-powertools-python/issues/1210)) + +## Documentation + +* **layer:** upgrade to 1.25.10 +* **roadmap:** add new roadmap section ([#1204](https://github.com/awslabs/aws-lambda-powertools-python/issues/1204)) + +## Features + +* **parameters:** accept boto3_client to support private endpoints and ease testing ([#1096](https://github.com/awslabs/aws-lambda-powertools-python/issues/1096)) + +## Maintenance + +* bump to 1.26.0 +* **deps:** bump pydantic from 1.9.0 to 1.9.1 ([#1221](https://github.com/awslabs/aws-lambda-powertools-python/issues/1221)) +* **deps:** bump email-validator from 1.1.3 to 1.2.1 ([#1199](https://github.com/awslabs/aws-lambda-powertools-python/issues/1199)) * **deps-dev:** bump mypy-boto3-secretsmanager from 1.21.34 to 1.23.0.post1 ([#1218](https://github.com/awslabs/aws-lambda-powertools-python/issues/1218)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.21.34 to 1.23.0.post1 ([#1219](https://github.com/awslabs/aws-lambda-powertools-python/issues/1219)) -* **deps-dev:** bump mypy-boto3-ssm from 1.21.34 to 1.23.0.post1 ([#1220](https://github.com/awslabs/aws-lambda-powertools-python/issues/1220)) -* **deps-dev:** bump mypy-boto3-ssm from 1.25.0 to 1.26.0.post1 ([#1690](https://github.com/awslabs/aws-lambda-powertools-python/issues/1690)) -* **deps-dev:** bump flake8-bugbear from 22.10.25 to 22.10.27 ([#1665](https://github.com/awslabs/aws-lambda-powertools-python/issues/1665)) -* **deps-dev:** bump mkdocs-material from 9.0.11 to 9.0.12 ([#1919](https://github.com/awslabs/aws-lambda-powertools-python/issues/1919)) -* **deps-dev:** bump mypy-boto3-appconfigdata from 1.26.0.post1 to 1.26.70 ([#1925](https://github.com/awslabs/aws-lambda-powertools-python/issues/1925)) -* **deps-dev:** bump flake8-bugbear from 23.1.20 to 23.2.13 ([#1924](https://github.com/awslabs/aws-lambda-powertools-python/issues/1924)) -* **deps-dev:** bump flake8-bugbear from 22.1.11 to 22.4.25 ([#1156](https://github.com/awslabs/aws-lambda-powertools-python/issues/1156)) -* **deps-dev:** bump mypy from 0.942 to 0.950 ([#1162](https://github.com/awslabs/aws-lambda-powertools-python/issues/1162)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.21.34 to 1.23.0.post1 ([#1219](https://github.com/awslabs/aws-lambda-powertools-python/issues/1219)) +* **deps-dev:** bump mypy-boto3-ssm from 1.21.34 to 1.23.0.post1 ([#1220](https://github.com/awslabs/aws-lambda-powertools-python/issues/1220)) + + + +## [v1.25.10] - 2022-04-29 +## Bug Fixes + +* **data-classes:** Add missing SES fields and ([#1045](https://github.com/awslabs/aws-lambda-powertools-python/issues/1045)) +* **deps:** Ignore boto3 changes until needed ([#1151](https://github.com/awslabs/aws-lambda-powertools-python/issues/1151)) +* **deps-dev:** remove jmespath due to dev deps conflict ([#1148](https://github.com/awslabs/aws-lambda-powertools-python/issues/1148)) +* **event_handler:** exception_handler to handle ServiceError exceptions ([#1160](https://github.com/awslabs/aws-lambda-powertools-python/issues/1160)) +* **event_handler:** Allow for event_source support ([#1159](https://github.com/awslabs/aws-lambda-powertools-python/issues/1159)) +* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) + +## Documentation + +* **layer:** upgrade to 1.25.9 + +## Features + +* **parameters:** add clear_cache method for providers ([#1194](https://github.com/awslabs/aws-lambda-powertools-python/issues/1194)) + +## Maintenance + +* include regression in changelog +* bump to 1.25.10 +* **ci:** changelog pre-generation to fetch tags from origin +* **ci:** disable mergify configuration after breaking changes ([#1188](https://github.com/awslabs/aws-lambda-powertools-python/issues/1188)) +* **ci:** post release on tagged issues too +* **deps:** bump codecov/codecov-action from 3.0.0 to 3.1.0 ([#1143](https://github.com/awslabs/aws-lambda-powertools-python/issues/1143)) +* **deps:** bump github/codeql-action from 1 to 2 ([#1154](https://github.com/awslabs/aws-lambda-powertools-python/issues/1154)) * **deps-dev:** bump flake8-eradicate from 1.2.0 to 1.2.1 ([#1158](https://github.com/awslabs/aws-lambda-powertools-python/issues/1158)) -* **deps-dev:** bump mypy-boto3-secretsmanager from 1.25.0 to 1.26.0.post1 ([#1691](https://github.com/awslabs/aws-lambda-powertools-python/issues/1691)) -* **deps-dev:** bump mypy-boto3-appconfig from 1.26.63 to 1.26.71 ([#1928](https://github.com/awslabs/aws-lambda-powertools-python/issues/1928)) -* **deps-dev:** bump pytest-benchmark from 3.4.1 to 4.0.0 ([#1659](https://github.com/awslabs/aws-lambda-powertools-python/issues/1659)) -* **deps-dev:** bump mypy-boto3-ssm from 1.26.43 to 1.26.77 ([#1949](https://github.com/awslabs/aws-lambda-powertools-python/issues/1949)) -* **deps-dev:** bump types-requests from 2.28.11.2 to 2.28.11.3 ([#1698](https://github.com/awslabs/aws-lambda-powertools-python/issues/1698)) +* **deps-dev:** bump mypy from 0.942 to 0.950 ([#1162](https://github.com/awslabs/aws-lambda-powertools-python/issues/1162)) +* **deps-dev:** bump mkdocs-git-revision-date-plugin ([#1146](https://github.com/awslabs/aws-lambda-powertools-python/issues/1146)) +* **deps-dev:** bump flake8-bugbear from 22.1.11 to 22.4.25 ([#1156](https://github.com/awslabs/aws-lambda-powertools-python/issues/1156)) * **deps-dev:** bump xenon from 0.8.0 to 0.9.0 ([#1145](https://github.com/awslabs/aws-lambda-powertools-python/issues/1145)) * **deps-dev:** bump mypy from 0.931 to 0.942 ([#1133](https://github.com/awslabs/aws-lambda-powertools-python/issues/1133)) -* **deps-dev:** bump types-requests from 2.28.11.12 to 2.28.11.13 ([#1933](https://github.com/awslabs/aws-lambda-powertools-python/issues/1933)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.6 to 2.8.19.7 ([#1932](https://github.com/awslabs/aws-lambda-powertools-python/issues/1932)) + +## Regression + +* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) ([#1190](https://github.com/awslabs/aws-lambda-powertools-python/issues/1190)) + + + +## [v1.25.9] - 2022-04-21 +## Bug Fixes + +* **deps:** correct py36 marker for jmespath + +## Maintenance + +* bump to 1.25.9 + + + +## [v1.25.8] - 2022-04-21 +## Bug Fixes + +* removed ambiguous quotes from labels. +* **deps:** update jmespath marker to support 1.0 and py3.6 ([#1139](https://github.com/awslabs/aws-lambda-powertools-python/issues/1139)) +* **governance:** update label in names in issues + +## Documentation + +* **install:** instructions to reduce pydantic package size ([#1077](https://github.com/awslabs/aws-lambda-powertools-python/issues/1077)) +* **layer:** remove link from clipboard button ([#1135](https://github.com/awslabs/aws-lambda-powertools-python/issues/1135)) +* **layer:** update to 1.25.7 + +## Maintenance + +* bump to 1.25.8 +* **deps:** bump codecov/codecov-action from 2.1.0 to 3.0.0 ([#1102](https://github.com/awslabs/aws-lambda-powertools-python/issues/1102)) +* **deps:** bump actions/upload-artifact from 2 to 3 ([#1103](https://github.com/awslabs/aws-lambda-powertools-python/issues/1103)) * **deps-dev:** bump mkdocs-material from 8.2.4 to 8.2.7 ([#1131](https://github.com/awslabs/aws-lambda-powertools-python/issues/1131)) * **deps-dev:** bump pytest from 6.2.5 to 7.0.1 ([#1063](https://github.com/awslabs/aws-lambda-powertools-python/issues/1063)) -* **deps-dev:** bump flake8-comprehensions from 3.10.0 to 3.10.1 ([#1699](https://github.com/awslabs/aws-lambda-powertools-python/issues/1699)) -* **deps-dev:** bump mkdocs-material from 8.5.7 to 8.5.9 ([#1697](https://github.com/awslabs/aws-lambda-powertools-python/issues/1697)) -* **deps-dev:** bump aws-cdk-lib from 2.64.0 to 2.65.0 ([#1938](https://github.com/awslabs/aws-lambda-powertools-python/issues/1938)) -* **deps-dev:** bump pytest-xdist from 2.5.0 to 3.0.2 ([#1655](https://github.com/awslabs/aws-lambda-powertools-python/issues/1655)) -* **deps-dev:** bump aws-cdk-lib from 2.65.0 to 2.66.0 ([#1948](https://github.com/awslabs/aws-lambda-powertools-python/issues/1948)) -* **deps-dev:** bump types-requests from 2.28.11.13 to 2.28.11.14 ([#1946](https://github.com/awslabs/aws-lambda-powertools-python/issues/1946)) -* **deps-dev:** bump types-python-dateutil from 2.8.19.7 to 2.8.19.8 ([#1945](https://github.com/awslabs/aws-lambda-powertools-python/issues/1945)) -* **deps-dev:** bump mkdocs-material from 9.0.10 to 9.0.11 ([#1896](https://github.com/awslabs/aws-lambda-powertools-python/issues/1896)) -* **deps-dev:** bump mkdocs-material from 9.0.12 to 9.0.13 ([#1944](https://github.com/awslabs/aws-lambda-powertools-python/issues/1944)) -* **deps-dev:** bump mkdocs-git-revision-date-plugin ([#1146](https://github.com/awslabs/aws-lambda-powertools-python/issues/1146)) -* **docs:** remove v2 banner on top of the docs -* **docs:** bump layer version to 36 (1.29.2) -* **docs:** remove pause sentence from roadmap ([#1409](https://github.com/awslabs/aws-lambda-powertools-python/issues/1409)) -* **docs:** update site name to test ci changelog -* **docs:** update description to trigger changelog generation -* **docs:** update CHANGELOG for v1.26.7 -* **governance:** update rfc to a form -* **governance:** update external non-triage effort disclaimer + + + +## [v1.25.7] - 2022-04-08 +## Bug Fixes + +* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) +* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/awslabs/aws-lambda-powertools-python/issues/1099)) +* **idempotency:** pass by value on idem key to guard inadvert mutations ([#1090](https://github.com/awslabs/aws-lambda-powertools-python/issues/1090)) +* **logger:** clear_state should keep custom key formats ([#1095](https://github.com/awslabs/aws-lambda-powertools-python/issues/1095)) +* **middleware_factory:** ret type annotation for handler dec ([#1066](https://github.com/awslabs/aws-lambda-powertools-python/issues/1066)) + +## Documentation + +* **layer:** update to 1.25.6; cosmetic changes + +## Maintenance + +* bump to 1.25.7 * **governance:** refresh pull request template sections -* **governance:** update bug report to a form -* **governance:** remove 'area/' from PR labels +* **governance:** update external non-triage effort disclaimer * **governance:** update static typing to a form -* **governance:** warn message on closed issues -* **governance:** warn message on closed issues -* **governance:** auto-merge mypy-stub dependabot -* **governance:** auto-merge to use squash -* **governance:** auto-merge workflow_dispatch off -* **governance:** auto-merge on all PR events -* **governance:** update wording tech debt to summary in maintenance template -* **governance:** add release label on pr merge -* **governance:** fix workflow action requirements & syntax -* **governance:** enforce safe scope on pr merge labelling -* **governance:** limit build workflow to code changes only -* **governance:** check for related issue in new PRs -* **governance:** address gh reusable workflow limitation -* **governance:** add pre-configured dev environment with GitPod.io to ease contributions ([#1403](https://github.com/awslabs/aws-lambda-powertools-python/issues/1403)) -* **governance:** remove markdown rendering from docs issue template -* **governance:** fix typo on semantic commit link introduced in [#1](https://github.com/awslabs/aws-lambda-powertools-python/issues/1)aef4 -* **governance:** fix on_merged_pr workflow syntax -* **governance:** add new maintenance issue template for tech debt ([#1326](https://github.com/awslabs/aws-lambda-powertools-python/issues/1326)) -* **governance:** update docs report to a form +* **governance:** update rfc to a form * **governance:** update feat request to a form * **governance:** bug report form typo -* **governance:** new static typing report -* **governance:** remove devcontainer in favour of gitpod.io ([#1411](https://github.com/awslabs/aws-lambda-powertools-python/issues/1411)) +* **governance:** update docs report to a form +* **governance:** update bug report to a form * **governance:** new ask a question -* **governance:** update emeritus affiliation -* **governance:** remove any step relying on master branch -* **layer:** bump to latest version 37 -* **layer:** remove unsused GetFunction permission for the canary -* **layer:** bump to 1.31.1 (v39) -* **layers:** bump to 21 for 1.26.2 -* **layers:** bump to 1.26.5 -* **layers:** bump to 1.26.6 using layer v26 -* **layers:** add dummy v2 layer automation -* **layers:** layer canary stack should not hardcode resource name -* **layers:** replace layers account secret ([#1329](https://github.com/awslabs/aws-lambda-powertools-python/issues/1329)) -* **layers:** expand to all aws commercial regions ([#1324](https://github.com/awslabs/aws-lambda-powertools-python/issues/1324)) -* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) -* **layers:** bump to 22 for 1.26.3 -* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/awslabs/aws-lambda-powertools-python/issues/1278)) -* **layers:** bump to 22 for 1.26.3 -* **layers:** upgrade cdk dep hashes to prevent ci fail +* **governance:** new static typing report + + + +## [v1.25.6] - 2022-04-01 +## Bug Fixes + +* **logger:** clear_state regression on absent standard keys ([#1088](https://github.com/awslabs/aws-lambda-powertools-python/issues/1088)) + +## Documentation + +* **layer:** bump to 1.25.5 + +## Maintenance + +* bump to 1.25.6 + + + +## [v1.25.5] - 2022-03-18 +## Bug Fixes + +* **logger-utils:** regression on exclude set leading to no formatter ([#1080](https://github.com/awslabs/aws-lambda-powertools-python/issues/1080)) + +## Maintenance + +* bump to 1.25.5 + + + +## [v1.25.4] - 2022-03-17 +## Bug Fixes + +* package_logger as const over logger instance +* repurpose test to cover parent loggers case +* use addHandler over monkeypatch + +## Documentation + +* **appsync:** fix typo +* **contributing:** operational excellence pause +* **layer:** update to 1.25.3 + +## Maintenance + +* bump to 1.25.4 +* remove duplicate test +* comment reason for change +* remove unnecessary test +* lint unused import + +## Regression + +* service_name fixture + +## Pull Requests + +* Merge pull request [#1075](https://github.com/awslabs/aws-lambda-powertools-python/issues/1075) from mploski/fix/existing-loggers-duplicated-logs + + + +## [v1.25.3] - 2022-03-09 +## Bug Fixes + +* **logger:** ensure state is cleared for custom formatters ([#1072](https://github.com/awslabs/aws-lambda-powertools-python/issues/1072)) + +## Documentation + +* **plugin:** add mermaid to create diagram as code ([#1070](https://github.com/awslabs/aws-lambda-powertools-python/issues/1070)) + +## Maintenance + +* bump to 1.25.3 + + + +## [v1.25.2] - 2022-03-07 +## Bug Fixes + +* **event_handler:** docs snippets, high-level import CorsConfig ([#1019](https://github.com/awslabs/aws-lambda-powertools-python/issues/1019)) +* **lambda-authorizer:** allow proxy resources path in arn ([#1051](https://github.com/awslabs/aws-lambda-powertools-python/issues/1051)) +* **metrics:** flush upon a single metric 100th data point ([#1046](https://github.com/awslabs/aws-lambda-powertools-python/issues/1046)) + +## Documentation + +* **layer:** update to 1.25.1 +* **parser:** APIGatewayProxyEvent to APIGatewayProxyEventModel ([#1061](https://github.com/awslabs/aws-lambda-powertools-python/issues/1061)) + +## Maintenance + +* bump to 1.25.2 +* **deps:** bump actions/setup-python from 2.3.1 to 3 ([#1048](https://github.com/awslabs/aws-lambda-powertools-python/issues/1048)) +* **deps:** bump actions/checkout from 2 to 3 ([#1052](https://github.com/awslabs/aws-lambda-powertools-python/issues/1052)) +* **deps:** bump actions/github-script from 5 to 6 ([#1023](https://github.com/awslabs/aws-lambda-powertools-python/issues/1023)) +* **deps:** bump fastjsonschema from 2.15.2 to 2.15.3 ([#949](https://github.com/awslabs/aws-lambda-powertools-python/issues/949)) +* **deps-dev:** bump mkdocs-material from 8.1.9 to 8.2.4 ([#1054](https://github.com/awslabs/aws-lambda-powertools-python/issues/1054)) + + + +## [v1.25.1] - 2022-02-14 +## Bug Fixes + +* **batch:** bugfix to clear exceptions between executions ([#1022](https://github.com/awslabs/aws-lambda-powertools-python/issues/1022)) + +## Maintenance + +* bump to 1.25.1 * **layers:** bump to 10 for 1.25.0 -* **lint:** use new isort black integration -* **logger:** overload inject_lambda_context with generics ([#1583](https://github.com/awslabs/aws-lambda-powertools-python/issues/1583)) -* **logger:** uncaught exception to use exception value as message -* **maintainer:** add Leandro as maintainer ([#1468](https://github.com/awslabs/aws-lambda-powertools-python/issues/1468)) -* **maintainers:** fix release workflow rename -* **maintainers:** update release workflow link -* **maintainers:** add Ruben as a maintainer ([#1392](https://github.com/awslabs/aws-lambda-powertools-python/issues/1392)) -* **maintenance:** add discord link to first PR and first issue ([#1493](https://github.com/awslabs/aws-lambda-powertools-python/issues/1493)) + + + +## [v1.25.0] - 2022-02-09 +## Bug Fixes + +* **apigateway:** remove indentation in debug_mode ([#987](https://github.com/awslabs/aws-lambda-powertools-python/issues/987)) +* **batch:** delete >10 messages in legacy sqs processor ([#818](https://github.com/awslabs/aws-lambda-powertools-python/issues/818)) +* **ci:** pr label regex for special chars in title +* **logger:** exclude source_logger in copy_config_to_registered_loggers ([#1001](https://github.com/awslabs/aws-lambda-powertools-python/issues/1001)) +* **logger:** test generates logfile + +## Documentation + +* fix syntax errors and line highlights ([#1004](https://github.com/awslabs/aws-lambda-powertools-python/issues/1004)) +* add better BDD coments +* **event-handler:** improve testing section for graphql ([#996](https://github.com/awslabs/aws-lambda-powertools-python/issues/996)) +* **layer:** update to 1.24.2 +* **parameters:** add testing your code section ([#1017](https://github.com/awslabs/aws-lambda-powertools-python/issues/1017)) +* **theme:** upgrade mkdocs-material to 8.x ([#1002](https://github.com/awslabs/aws-lambda-powertools-python/issues/1002)) +* **tutorial:** fix broken internal links ([#1000](https://github.com/awslabs/aws-lambda-powertools-python/issues/1000)) + +## Features + +* **event-handler:** new resolvers to fix current_event typing ([#978](https://github.com/awslabs/aws-lambda-powertools-python/issues/978)) +* **logger:** log_event support event data classes (e.g. S3Event) ([#984](https://github.com/awslabs/aws-lambda-powertools-python/issues/984)) +* **mypy:** complete mypy support for the entire codebase ([#943](https://github.com/awslabs/aws-lambda-powertools-python/issues/943)) + +## Maintenance + +* bump to 1.25.0 +* correct docs +* correct docs +* use isinstance over type +* **deps-dev:** bump flake8-bugbear from 21.11.29 to 22.1.11 ([#955](https://github.com/awslabs/aws-lambda-powertools-python/issues/955)) * **metrics:** fix tests when warnings are disabled ([#994](https://github.com/awslabs/aws-lambda-powertools-python/issues/994)) -* **metrics:** revert dimensions test before splitting ([#1243](https://github.com/awslabs/aws-lambda-powertools-python/issues/1243)) -* **multiple:** localize powertools_dev env logic and warning ([#1570](https://github.com/awslabs/aws-lambda-powertools-python/issues/1570)) -* **package:** correct pyproject version manually -* **parser:** add workaround to make API GW test button work ([#1971](https://github.com/awslabs/aws-lambda-powertools-python/issues/1971)) -* **pypi:** add new links to Pypi package homepage ([#1912](https://github.com/awslabs/aws-lambda-powertools-python/issues/1912)) -* **test-perf:** use pytest-benchmark to improve reliability ([#1250](https://github.com/awslabs/aws-lambda-powertools-python/issues/1250)) -* **tests:** enable end-to-end test workflow ([#1470](https://github.com/awslabs/aws-lambda-powertools-python/issues/1470)) -* **tests:** build and deploy Lambda Layer stack once ([#1466](https://github.com/awslabs/aws-lambda-powertools-python/issues/1466)) -* **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) -* **tests:** refactor E2E logger to ease maintenance, writing tests and parallelization ([#1460](https://github.com/awslabs/aws-lambda-powertools-python/issues/1460)) -* **tests:** move shared_functions to unit tests -* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) + +## Pull Requests + +* Merge pull request [#971](https://github.com/awslabs/aws-lambda-powertools-python/issues/971) from gyft/fix-logger-util-tests + + + +## [v1.24.2] - 2022-01-21 +## Bug Fixes + +* **data-classes:** underscore support in api gateway authorizer resource name ([#969](https://github.com/awslabs/aws-lambda-powertools-python/issues/969)) + +## Documentation + +* **layer:** update to 1.24.1 + +## Maintenance + +* bump to 1.24.2 + + + +## [v1.24.1] - 2022-01-20 +## Bug Fixes + +* remove unused json import +* remove apigw contract when using event-handler, apigw tracing +* use decorators, split cold start to ease reading +* incorrect log keys, indentation, snippet consistency +* remove f-strings that doesn't evaluate expr +* **batch:** report multiple failures ([#967](https://github.com/awslabs/aws-lambda-powertools-python/issues/967)) +* **data-classes:** docstring typos and clean up ([#937](https://github.com/awslabs/aws-lambda-powertools-python/issues/937)) +* **parameters:** appconfig internal _get docstrings ([#934](https://github.com/awslabs/aws-lambda-powertools-python/issues/934)) + +## Documentation + +* rename quickstart to tutorial in readme +* rename to tutorial given the size +* add final consideration section +* **batch:** snippet typo on batch processed messages iteration ([#951](https://github.com/awslabs/aws-lambda-powertools-python/issues/951)) +* **batch:** fix typo in context manager keyword ([#938](https://github.com/awslabs/aws-lambda-powertools-python/issues/938)) +* **homepage:** link to typescript version ([#950](https://github.com/awslabs/aws-lambda-powertools-python/issues/950)) +* **install:** new lambda layer for 1.24.0 release +* **metrics:** keep it consistent with other sections, update metric names +* **nav:** make REST and GraphQL event handlers more explicit ([#959](https://github.com/awslabs/aws-lambda-powertools-python/issues/959)) +* **quickstart:** expand on intro line +* **quickstart:** tidy requirements up +* **quickstart:** make section agnostic to json lib +* **quickstart:** same process for Logger +* **quickstart:** add sub-sections, fix highlight & code +* **quickstart:** sentence fragmentation, tidy up +* **tenets:** make core, non-core more explicit +* **tracer:** warning to note on local traces +* **tracer:** add initial image, requirements +* **tracer:** add annotation, metadata, and image +* **tracer:** update ServiceLens image w/ API GW, copywriting +* **tutorial:** fix path to images ([#963](https://github.com/awslabs/aws-lambda-powertools-python/issues/963)) + +## Features + +* **ci:** auto-notify & close issues on release +* **logger:** clone powertools logger config to any Python logger ([#927](https://github.com/awslabs/aws-lambda-powertools-python/issues/927)) + +## Maintenance + +* bump to 1.24.1 +* bump to 1.24.1 +* **ci:** run codeql analysis on push only +* **ci:** fix mergify dependabot queue +* **ci:** add queue name in mergify +* **ci:** remove mergify legacy key +* **ci:** update mergify bot breaking change +* **ci:** safely label PR based on title +* **deps:** bump pydantic from 1.8.2 to 1.9.0 ([#933](https://github.com/awslabs/aws-lambda-powertools-python/issues/933)) +* **deps-dev:** bump mypy from 0.930 to 0.931 ([#941](https://github.com/awslabs/aws-lambda-powertools-python/issues/941)) ## Regression -* service_name fixture * order to APP logger/service name due to screenshots -* **ci:** new gh-pages beta doesn't work either; reverting as gh-pages is disrupted -* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/awslabs/aws-lambda-powertools-python/issues/1027)) ([#1190](https://github.com/awslabs/aws-lambda-powertools-python/issues/1190)) + +## Pull Requests + +* Merge pull request [#769](https://github.com/awslabs/aws-lambda-powertools-python/issues/769) from mploski/docs/quick-start + + + +## [v1.24.0] - 2021-12-31 +## Bug Fixes + +* **apigateway:** support [@app](https://github.com/app).not_found() syntax & housekeeping ([#926](https://github.com/awslabs/aws-lambda-powertools-python/issues/926)) +* **event-sources:** handle dynamodb null type as none, not bool ([#929](https://github.com/awslabs/aws-lambda-powertools-python/issues/929)) +* **warning:** future distutils deprecation ([#921](https://github.com/awslabs/aws-lambda-powertools-python/issues/921)) + +## Documentation + +* consistency around admonitions and snippets ([#919](https://github.com/awslabs/aws-lambda-powertools-python/issues/919)) +* Added GraphQL Sample API to Examples section of README.md ([#930](https://github.com/awslabs/aws-lambda-powertools-python/issues/930)) +* **batch:** remove leftover from legacy +* **layer:** bump Lambda Layer to version 6 +* **tracer:** new ignore_endpoint feature ([#931](https://github.com/awslabs/aws-lambda-powertools-python/issues/931)) + +## Features + +* **event-sources:** cache parsed json in data class ([#909](https://github.com/awslabs/aws-lambda-powertools-python/issues/909)) +* **feature_flags:** support beyond boolean values (JSON values) ([#804](https://github.com/awslabs/aws-lambda-powertools-python/issues/804)) +* **idempotency:** support dataclasses & pydantic models payloads ([#908](https://github.com/awslabs/aws-lambda-powertools-python/issues/908)) +* **logger:** support use_datetime_directive for timestamps ([#920](https://github.com/awslabs/aws-lambda-powertools-python/issues/920)) +* **tracer:** ignore tracing for certain hostname(s) or url(s) ([#910](https://github.com/awslabs/aws-lambda-powertools-python/issues/910)) + +## Maintenance + +* bump to 1.24.0 +* **deps-dev:** bump mypy from 0.920 to 0.930 ([#925](https://github.com/awslabs/aws-lambda-powertools-python/issues/925)) + + + +## [v1.23.0] - 2021-12-20 +## Bug Fixes + +* **apigateway:** allow list of HTTP methods in route method ([#838](https://github.com/awslabs/aws-lambda-powertools-python/issues/838)) +* **event-sources:** Pass authorizer data to APIGatewayEventAuthorizer ([#897](https://github.com/awslabs/aws-lambda-powertools-python/issues/897)) +* **event-sources:** handle claimsOverrideDetails set to null ([#878](https://github.com/awslabs/aws-lambda-powertools-python/issues/878)) +* **idempotency:** include decorated fn name in hash ([#869](https://github.com/awslabs/aws-lambda-powertools-python/issues/869)) +* **metrics:** explicit type to single_metric ctx manager ([#865](https://github.com/awslabs/aws-lambda-powertools-python/issues/865)) +* **parameters:** appconfig transform and return types ([#877](https://github.com/awslabs/aws-lambda-powertools-python/issues/877)) +* **parser:** overload parse when using envelope ([#885](https://github.com/awslabs/aws-lambda-powertools-python/issues/885)) +* **parser:** kinesis sequence number is str, not int ([#907](https://github.com/awslabs/aws-lambda-powertools-python/issues/907)) +* **parser:** mypy support for payload type override as models ([#883](https://github.com/awslabs/aws-lambda-powertools-python/issues/883)) +* **tracer:** add warm start annotation (ColdStart=False) ([#851](https://github.com/awslabs/aws-lambda-powertools-python/issues/851)) + +## Documentation + +* external reference to cloudformation custom resource helper ([#914](https://github.com/awslabs/aws-lambda-powertools-python/issues/914)) +* add new public Slack invite +* disable search blur in non-prod env +* update Lambda Layers version +* **apigateway:** add new not_found feature ([#915](https://github.com/awslabs/aws-lambda-powertools-python/issues/915)) +* **apigateway:** fix sample layout provided ([#864](https://github.com/awslabs/aws-lambda-powertools-python/issues/864)) +* **appsync:** fix users.py typo to locations [#830](https://github.com/awslabs/aws-lambda-powertools-python/issues/830) +* **lambda_layer:** fix CDK layer syntax + +## Features + +* **apigateway:** add exception_handler support ([#898](https://github.com/awslabs/aws-lambda-powertools-python/issues/898)) +* **apigateway:** access parent api resolver from router ([#842](https://github.com/awslabs/aws-lambda-powertools-python/issues/842)) +* **batch:** new BatchProcessor for SQS, DynamoDB, Kinesis ([#886](https://github.com/awslabs/aws-lambda-powertools-python/issues/886)) +* **logger:** allow handler with custom kwargs signature ([#913](https://github.com/awslabs/aws-lambda-powertools-python/issues/913)) +* **tracer:** add service annotation when service is set ([#861](https://github.com/awslabs/aws-lambda-powertools-python/issues/861)) + +## Maintenance + +* correct pr label order +* minor housekeeping before release ([#912](https://github.com/awslabs/aws-lambda-powertools-python/issues/912)) +* bump to 1.23.0 +* **ci:** split latest docs workflow +* **deps:** bump fastjsonschema from 2.15.1 to 2.15.2 ([#891](https://github.com/awslabs/aws-lambda-powertools-python/issues/891)) +* **deps:** bump actions/setup-python from 2.2.2 to 2.3.0 ([#831](https://github.com/awslabs/aws-lambda-powertools-python/issues/831)) +* **deps:** bump aws-xray-sdk from 2.8.0 to 2.9.0 ([#876](https://github.com/awslabs/aws-lambda-powertools-python/issues/876)) +* **deps:** support arm64 when developing locally ([#862](https://github.com/awslabs/aws-lambda-powertools-python/issues/862)) +* **deps:** bump actions/setup-python from 2.3.0 to 2.3.1 ([#852](https://github.com/awslabs/aws-lambda-powertools-python/issues/852)) +* **deps-dev:** bump flake8 from 3.9.2 to 4.0.1 ([#789](https://github.com/awslabs/aws-lambda-powertools-python/issues/789)) +* **deps-dev:** bump black from 21.10b0 to 21.11b1 ([#839](https://github.com/awslabs/aws-lambda-powertools-python/issues/839)) +* **deps-dev:** bump black from 21.11b1 to 21.12b0 ([#872](https://github.com/awslabs/aws-lambda-powertools-python/issues/872)) +* **deps-dev:** bump mypy from 0.910 to 0.920 ([#903](https://github.com/awslabs/aws-lambda-powertools-python/issues/903)) + + + +## [v1.22.0] - 2021-11-17 +## Bug Fixes + +* change supported python version from 3.6.1 to 3.6.2, bump black ([#807](https://github.com/awslabs/aws-lambda-powertools-python/issues/807)) +* **ci:** comment custom publish version checker +* **ci:** skip sync master on docs hotfix +* **parser:** body/QS can be null or omitted in apigw v1/v2 ([#820](https://github.com/awslabs/aws-lambda-powertools-python/issues/820)) + +## Code Refactoring + +* **apigateway:** Add BaseRouter and duplicate route check ([#757](https://github.com/awslabs/aws-lambda-powertools-python/issues/757)) + +## Documentation + +* updated Lambda Layers definition & limitations. ([#775](https://github.com/awslabs/aws-lambda-powertools-python/issues/775)) +* Idiomatic tenet updated to Progressive +* use higher contrast font ([#822](https://github.com/awslabs/aws-lambda-powertools-python/issues/822)) +* use higher contrast font +* fix indentation of SAM snippets in install section ([#778](https://github.com/awslabs/aws-lambda-powertools-python/issues/778)) +* improve public lambda layer wording, clipboard buttons ([#762](https://github.com/awslabs/aws-lambda-powertools-python/issues/762)) +* add amplify-cli instructions for public layer ([#754](https://github.com/awslabs/aws-lambda-powertools-python/issues/754)) +* **api-gateway:** add support for new router feature ([#767](https://github.com/awslabs/aws-lambda-powertools-python/issues/767)) +* **apigateway:** re-add sample layout, add considerations ([#826](https://github.com/awslabs/aws-lambda-powertools-python/issues/826)) +* **appsync:** add new router feature ([#821](https://github.com/awslabs/aws-lambda-powertools-python/issues/821)) +* **idempotency:** add support for DynamoDB composite keys ([#808](https://github.com/awslabs/aws-lambda-powertools-python/issues/808)) +* **tenets:** update Idiomatic tenet to Progressive ([#823](https://github.com/awslabs/aws-lambda-powertools-python/issues/823)) + +## Features + +* **apigateway:** add Router to allow large routing composition ([#645](https://github.com/awslabs/aws-lambda-powertools-python/issues/645)) +* **appsync:** add Router to allow large resolver composition ([#776](https://github.com/awslabs/aws-lambda-powertools-python/issues/776)) +* **data-classes:** ActiveMQ and RabbitMQ support ([#770](https://github.com/awslabs/aws-lambda-powertools-python/issues/770)) +* **logger:** add ALB correlation ID support ([#816](https://github.com/awslabs/aws-lambda-powertools-python/issues/816)) + +## Maintenance + +* fix var expr +* remove Lambda Layer version tag +* bump to 1.22.0 +* conditional to publish docs only attempt 3 +* conditional to publish docs only attempt 2 +* conditional to publish docs only +* **deps:** bump boto3 from 1.18.58 to 1.18.59 ([#760](https://github.com/awslabs/aws-lambda-powertools-python/issues/760)) +* **deps:** bump boto3 from 1.18.56 to 1.18.58 ([#755](https://github.com/awslabs/aws-lambda-powertools-python/issues/755)) +* **deps:** bump urllib3 from 1.26.4 to 1.26.5 ([#787](https://github.com/awslabs/aws-lambda-powertools-python/issues/787)) +* **deps:** bump boto3 from 1.19.6 to 1.20.3 ([#809](https://github.com/awslabs/aws-lambda-powertools-python/issues/809)) +* **deps:** bump boto3 from 1.18.61 to 1.19.6 ([#783](https://github.com/awslabs/aws-lambda-powertools-python/issues/783)) +* **deps:** bump boto3 from 1.20.3 to 1.20.5 ([#817](https://github.com/awslabs/aws-lambda-powertools-python/issues/817)) +* **deps:** bump boto3 from 1.18.59 to 1.18.61 ([#766](https://github.com/awslabs/aws-lambda-powertools-python/issues/766)) +* **deps-dev:** bump coverage from 6.0.1 to 6.0.2 ([#764](https://github.com/awslabs/aws-lambda-powertools-python/issues/764)) +* **deps-dev:** bump pytest-asyncio from 0.15.1 to 0.16.0 ([#782](https://github.com/awslabs/aws-lambda-powertools-python/issues/782)) +* **deps-dev:** bump flake8-eradicate from 1.1.0 to 1.2.0 ([#784](https://github.com/awslabs/aws-lambda-powertools-python/issues/784)) +* **deps-dev:** bump flake8-isort from 4.0.0 to 4.1.1 ([#785](https://github.com/awslabs/aws-lambda-powertools-python/issues/785)) +* **deps-dev:** bump mkdocs-material from 7.3.2 to 7.3.3 ([#758](https://github.com/awslabs/aws-lambda-powertools-python/issues/758)) +* **deps-dev:** bump flake8-comprehensions from 3.6.1 to 3.7.0 ([#759](https://github.com/awslabs/aws-lambda-powertools-python/issues/759)) +* **deps-dev:** bump mkdocs-material from 7.3.3 to 7.3.5 ([#781](https://github.com/awslabs/aws-lambda-powertools-python/issues/781)) +* **deps-dev:** bump coverage from 6.0 to 6.0.1 ([#751](https://github.com/awslabs/aws-lambda-powertools-python/issues/751)) +* **deps-dev:** bump mkdocs-material from 7.3.5 to 7.3.6 ([#791](https://github.com/awslabs/aws-lambda-powertools-python/issues/791)) +* **deps-dev:** bump coverage from 6.0.2 to 6.1.2 ([#810](https://github.com/awslabs/aws-lambda-powertools-python/issues/810)) +* **deps-dev:** bump isort from 5.9.3 to 5.10.1 ([#811](https://github.com/awslabs/aws-lambda-powertools-python/issues/811)) @@ -2345,7 +2950,52 @@ * 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/v1.21.1...HEAD +[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.9.1...HEAD +[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 +[v2.8.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.7.1...v2.8.0 +[v2.7.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.7.0...v2.7.1 +[v2.7.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.6.0...v2.7.0 +[v2.6.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.5.0...v2.6.0 +[v2.5.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.4.0...v2.5.0 +[v2.4.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.3.1...v2.4.0 +[v2.3.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.3.0...v2.3.1 +[v2.3.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.2.0...v2.3.0 +[v2.2.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.1.0...v2.2.0 +[v2.1.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.0.0...v2.1.0 +[v2.0.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.31.1...v2.0.0 +[v1.31.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.31.0...v1.31.1 +[v1.31.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.30.0...v1.31.0 +[v1.30.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.29.2...v1.30.0 +[v1.29.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.29.1...v1.29.2 +[v1.29.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.29.0...v1.29.1 +[v1.29.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.28.0...v1.29.0 +[v1.28.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.27.0...v1.28.0 +[v1.27.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.7...v1.27.0 +[v1.26.7]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.6...v1.26.7 +[v1.26.6]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.5...v1.26.6 +[v1.26.5]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.4...v1.26.5 +[v1.26.4]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.3...v1.26.4 +[v1.26.3]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.2...v1.26.3 +[v1.26.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.1...v1.26.2 +[v1.26.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.0...v1.26.1 +[v1.26.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.10...v1.26.0 +[v1.25.10]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.9...v1.25.10 +[v1.25.9]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.8...v1.25.9 +[v1.25.8]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.7...v1.25.8 +[v1.25.7]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.6...v1.25.7 +[v1.25.6]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.5...v1.25.6 +[v1.25.5]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.4...v1.25.5 +[v1.25.4]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.3...v1.25.4 +[v1.25.3]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.2...v1.25.3 +[v1.25.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.1...v1.25.2 +[v1.25.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.25.0...v1.25.1 +[v1.25.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.24.2...v1.25.0 +[v1.24.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.24.1...v1.24.2 +[v1.24.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.24.0...v1.24.1 +[v1.24.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.23.0...v1.24.0 +[v1.23.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.22.0...v1.23.0 +[v1.22.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.21.1...v1.22.0 [v1.21.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.21.0...v1.21.1 [v1.21.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.20.2...v1.21.0 [v1.20.2]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.20.1...v1.20.2 diff --git a/tests/e2e/event_handler/files/schema.graphql b/tests/e2e/event_handler/files/schema.graphql index b00e185ccfb..0558f161ed5 100644 --- a/tests/e2e/event_handler/files/schema.graphql +++ b/tests/e2e/event_handler/files/schema.graphql @@ -3,12 +3,12 @@ schema { } type Query { - getPost(id:ID!): Post + getPost(post_id:ID!): Post allPosts: [Post] } type Post { - id: ID! + post_id: ID! author: String! title: String content: String diff --git a/tests/e2e/event_handler/handlers/appsync_resolver_handler.py b/tests/e2e/event_handler/handlers/appsync_resolver_handler.py index 5c2147bf932..1a67f714e3d 100644 --- a/tests/e2e/event_handler/handlers/appsync_resolver_handler.py +++ b/tests/e2e/event_handler/handlers/appsync_resolver_handler.py @@ -1,34 +1,34 @@ from typing import List +from pydantic import BaseModel from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.utilities.typing import LambdaContext -from pydantic import BaseModel app = AppSyncResolver() posts = { "1": { - "id": "1", + "post_id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", - "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", + "content": "SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10", }, "2": { - "id": "2", + "post_id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", - "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", + "content": "SAMPLE TEXT AUTHOR 2", "ups": "100", "downs": "10", }, "3": { - "id": "3", + "post_id": "3", "title": "Third book", "author": "Author3", "url": None, @@ -37,20 +37,20 @@ "downs": None, }, "4": { - "id": "4", + "post_id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", - "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", + "content": "SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0", }, "5": { - "id": "5", + "post_id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", - "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", + "content": "SAMPLE TEXT AUTHOR 5", "ups": "50", "downs": "0", }, @@ -66,7 +66,7 @@ class Post(BaseModel): - id: str + post_id: str author: str title: str url: str @@ -76,15 +76,14 @@ class Post(BaseModel): @app.resolver(type_name="Query", field_name="getPost") -def get_post(id: str = "") -> dict: - post = Post(**posts[id]).dict() +def get_post(post_id: str = "") -> dict: + post = Post(**posts[post_id]).dict() return post @app.resolver(type_name="Query", field_name="allPosts") def all_posts() -> List[dict]: - parsed_posts = [post for post in posts.values()] - return parsed_posts + return list(posts.values()) @app.resolver(type_name="Post", field_name="relatedPosts") @@ -92,7 +91,7 @@ def related_posts() -> List[dict]: posts = [] for resolver_event in app.current_event: if resolver_event.source: - posts.append(posts_related[resolver_event.source["id"]]) + posts.append(posts_related[resolver_event.source["post_id"]]) return posts diff --git a/tests/e2e/event_handler/infrastructure.py b/tests/e2e/event_handler/infrastructure.py index 7f2c552cf10..5f69aa14e40 100644 --- a/tests/e2e/event_handler/infrastructure.py +++ b/tests/e2e/event_handler/infrastructure.py @@ -6,11 +6,10 @@ from aws_cdk import aws_apigatewayv2_alpha as apigwv2 from aws_cdk import aws_apigatewayv2_authorizers_alpha as apigwv2authorizers from aws_cdk import aws_apigatewayv2_integrations_alpha as apigwv2integrations +from aws_cdk import aws_appsync_alpha as appsync from aws_cdk import aws_ec2 as ec2 from aws_cdk import aws_elasticloadbalancingv2 as elbv2 from aws_cdk import aws_elasticloadbalancingv2_targets as targets -from aws_cdk import aws_appsync_alpha as appsync -from aws_cdk import aws_iam from aws_cdk.aws_lambda import Function, FunctionUrlAuthType from tests.e2e.utils.infrastructure import BaseInfrastructure @@ -20,10 +19,10 @@ class EventHandlerStack(BaseInfrastructure): def create_resources(self): functions = self.create_lambda_functions() - # self._create_alb(function=functions["AlbHandler"]) - # self._create_api_gateway_rest(function=functions["ApiGatewayRestHandler"]) - # self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"]) - # self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"]) + self._create_alb(function=functions["AlbHandler"]) + self._create_api_gateway_rest(function=functions["ApiGatewayRestHandler"]) + self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"]) + self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"]) self._create_appsync_endpoint(function=functions["AppsyncResolverHandler"]) def _create_alb(self, function: Function): diff --git a/tests/e2e/event_handler/test_appsync_resolvers.py b/tests/e2e/event_handler/test_appsync_resolvers.py index bc3c89d2be2..2e20959ec43 100644 --- a/tests/e2e/event_handler/test_appsync_resolvers.py +++ b/tests/e2e/event_handler/test_appsync_resolvers.py @@ -1,9 +1,9 @@ import json + import pytest -from requests import HTTPError, Request +from requests import Request from tests.e2e.utils import data_fetcher -from tests.e2e.utils.auth import build_iam_auth @pytest.fixture @@ -20,7 +20,7 @@ def appsync_access_key(infrastructure: dict) -> str: def test_appsync_get_all_posts(appsync_endpoint, appsync_access_key): # GIVEN body = { - "query": "query MyQuery { allPosts { id }}", + "query": "query MyQuery { allPosts { post_id }}", "variables": None, "operationName": "MyQuery", } @@ -50,7 +50,7 @@ def test_appsync_get_post(appsync_endpoint, appsync_access_key): # GIVEN post_id = "1" body = { - "query": f'query MyQuery {{ getPost(id: "{post_id}") {{ id }} }}', + "query": f'query MyQuery {{ getPost(post_id: "{post_id}") {{ post_id }} }}', "variables": None, "operationName": "MyQuery", } @@ -71,7 +71,7 @@ def test_appsync_get_post(appsync_endpoint, appsync_access_key): data = json.loads(response.content.decode("ascii"))["data"] - assert data["getPost"]["id"] == post_id + assert data["getPost"]["post_id"] == post_id @pytest.mark.xdist_group(name="event_handler") @@ -81,7 +81,7 @@ def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): related_posts_ids = ["3", "5"] body = { - "query": f'query MyQuery {{ getPost(id: "{post_id}") {{ id relatedPosts {{ id }} }} }}', + "query": f'query MyQuery {{ getPost(post_id: "{post_id}") {{ post_id relatedPosts {{ post_id }} }} }}', "variables": None, "operationName": "MyQuery", } @@ -102,7 +102,7 @@ def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): data = json.loads(response.content.decode("ascii"))["data"] - assert data["getPost"]["id"] == post_id + assert data["getPost"]["post_id"] == post_id assert len(data["getPost"]["relatedPosts"]) == len(related_posts_ids) for post in data["getPost"]["relatedPosts"]: - assert post["id"] in related_posts_ids + assert post["post_id"] in related_posts_ids From bc45703beda8155ca094dbe95ed76464afd6bf6d Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Thu, 23 Mar 2023 14:29:18 +0100 Subject: [PATCH 07/75] Refactor appsync resolver --- .../event_handler/appsync.py | 138 ++++++++++++------ .../src/custom_models.py | 3 +- .../handlers/appsync_resolver_handler.py | 13 +- .../functional/event_handler/test_appsync.py | 14 +- 4 files changed, 110 insertions(+), 58 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 4132cc3524d..7ed80f68804 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,22 +1,32 @@ import logging from itertools import groupby -from typing import Any, Callable, List, Optional, Type, TypeVar, Union +from typing import Any, Callable, List, Optional, Type, Union from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext logger = logging.getLogger(__name__) -AppSyncResolverEventT = TypeVar("AppSyncResolverEventT", bound=AppSyncResolverEvent) +class RouterContext: + def __init__(self): + super().__init__() + self.context = {} -class BaseRouter: - current_event: Union[AppSyncResolverEventT, List[AppSyncResolverEventT]] # type: ignore[valid-type] - lambda_context: LambdaContext - context: dict + def append_context(self, **additional_context): + """Append key=value data as routing context""" + self.context.update(**additional_context) + def clear_context(self): + """Resets routing context""" + self.context.clear() + + +class ResolverRegistry: def __init__(self): + super().__init__() self._resolvers: dict = {} + self._batch_resolvers: dict = {} def resolver(self, type_name: str = "*", field_name: Optional[str] = None): """Registers the resolver for field_name @@ -29,23 +39,33 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None): Field name """ - def register_resolver(func): + def register(func): logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") self._resolvers[f"{type_name}.{field_name}"] = {"func": func} return func - return register_resolver + return register - def append_context(self, **additional_context): - """Append key=value data as routing context""" - self.context.update(**additional_context) + def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None): + """Registers the resolver for field_name - def clear_context(self): - """Resets routing context""" - self.context.clear() + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + """ + def register(func): + logger.debug(f"Adding batch resolver `{func.__name__}` for field `{type_name}.{field_name}`") + self._batch_resolvers[f"{type_name}.{field_name}"] = {"func": func} + return func -class AppSyncResolver(BaseRouter): + return register + + +class AppSyncResolver(ResolverRegistry, RouterContext): """ AppSync resolver decorator @@ -78,16 +98,20 @@ def common_field() -> str: def __init__(self): super().__init__() - self.context = {} # early init as customers might add context before event resolution + self.current_batch_event: List[AppSyncResolverEvent] = [] + self.current_event: Optional[AppSyncResolverEvent] = None def resolve( - self, event: dict, context: LambdaContext, data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent + self, + event: Union[dict, List[dict]], + context: LambdaContext, + data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, ) -> Any: """Resolve field_name Parameters ---------- - event : dict + event : dict | List[dict] Lambda event context : LambdaContext Lambda context @@ -152,33 +176,38 @@ def lambda_handler(event, context): ValueError If we could not find a field resolver """ - # Maintenance: revisit generics/overload to fix [attr-defined] in mypy usage - - BaseRouter.lambda_context = context - - # If event is a list it means that AppSync sent batch request - if isinstance(event, list): - event_groups = [ - {"field_name": field_name, "events": list(events)} - for field_name, events in groupby(event, key=lambda x: x["info"]["fieldName"]) - ] - if len(event_groups) > 1: - ValueError("batch with different field names. It shouldn't happen!") - - appconfig_events = [data_model(event) for event in event_groups[0]["events"]] - BaseRouter.current_event = appconfig_events - resolver = self._get_resolver(appconfig_events[0].type_name, event_groups[0]["field_name"]) - response = resolver() - else: - appconfig_event = data_model(event) - BaseRouter.current_event = appconfig_event - resolver = self._get_resolver(appconfig_event.type_name, appconfig_event.field_name) - response = resolver(**appconfig_event.arguments) + self.lambda_context = context + + response = ( + self._call_batch_resolver(event, data_model) + if isinstance(event, list) + else self._call_resolver(event, data_model) + ) self.clear_context() return response + def _call_resolver(self, event: dict, data_model: Type[AppSyncResolverEvent]) -> Any: + self.current_event = data_model(event) + resolver = self._get_resolver(self.current_event.type_name, self.current_event.field_name) + return resolver(**self.current_event.arguments) + + def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolverEvent]) -> List[Any]: + event_groups = [ + {"field_name": field_name, "events": list(events)} + for field_name, events in groupby(event, key=lambda x: x["info"]["fieldName"]) + ] + if len(event_groups) > 1: + ValueError("batch with different field names. It shouldn't happen!") + + self.current_batch_event = [data_model(event) for event in event_groups[0]["events"]] + resolver = self._get_batch_resolver( + self.current_batch_event[0].type_name, self.current_batch_event[0].field_name + ) + + return [resolver(event=appconfig_event) for appconfig_event in self.current_batch_event] + def _get_resolver(self, type_name: str, field_name: str) -> Callable: """Get resolver for field_name @@ -200,8 +229,32 @@ def _get_resolver(self, type_name: str, field_name: str) -> Callable: raise ValueError(f"No resolver found for '{full_name}'") return resolver["func"] + def _get_batch_resolver(self, type_name: str, field_name: str) -> Callable: + """Get resolver for field_name + + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + + Returns + ------- + Callable + callable function and configuration + """ + full_name = f"{type_name}.{field_name}" + resolver = self._batch_resolvers.get(full_name, self._batch_resolvers.get(f"*.{field_name}")) + if not resolver: + raise ValueError(f"No batch resolver found for '{full_name}'") + return resolver["func"] + def __call__( - self, event: dict, context: LambdaContext, data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent + self, + event: Union[dict, List[dict]], + context: LambdaContext, + data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, ) -> Any: """Implicit lambda handler which internally calls `resolve`""" return self.resolve(event, context, data_model) @@ -222,7 +275,6 @@ def include_router(self, router: "Router") -> None: self._resolvers.update(router._resolvers) -class Router(BaseRouter): +class Router(RouterContext, ResolverRegistry): def __init__(self): super().__init__() - self.context = {} # early init as customers might add context before event resolution diff --git a/examples/event_handler_graphql/src/custom_models.py b/examples/event_handler_graphql/src/custom_models.py index 6d82e1ba9be..84982b0f81f 100644 --- a/examples/event_handler_graphql/src/custom_models.py +++ b/examples/event_handler_graphql/src/custom_models.py @@ -42,7 +42,8 @@ def api_key(self) -> str: @app.resolver(type_name="Query", field_name="listLocations") def list_locations(page: int = 0, size: int = 10) -> List[Location]: # additional properties/methods will now be available under current_event - logger.debug(f"Request country origin: {app.current_event.country_viewer}") # type: ignore[attr-defined] + if app.current_event: + logger.debug(f"Request country origin: {app.current_event.country_viewer}") # type: ignore[attr-defined] return [{"id": scalar_types_utils.make_id(), "name": "Perry, James and Carroll"}] diff --git a/tests/e2e/event_handler/handlers/appsync_resolver_handler.py b/tests/e2e/event_handler/handlers/appsync_resolver_handler.py index 1a67f714e3d..c904a86e490 100644 --- a/tests/e2e/event_handler/handlers/appsync_resolver_handler.py +++ b/tests/e2e/event_handler/handlers/appsync_resolver_handler.py @@ -1,8 +1,9 @@ -from typing import List +from typing import List, Optional from pydantic import BaseModel from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext app = AppSyncResolver() @@ -86,13 +87,9 @@ def all_posts() -> List[dict]: return list(posts.values()) -@app.resolver(type_name="Post", field_name="relatedPosts") -def related_posts() -> List[dict]: - posts = [] - for resolver_event in app.current_event: - if resolver_event.source: - posts.append(posts_related[resolver_event.source["post_id"]]) - return posts +@app.batch_resolver(type_name="Post", field_name="relatedPosts") +def related_posts(event: AppSyncResolverEvent) -> Optional[list]: + return posts_related[event.source["post_id"]] if event.source else None def lambda_handler(event, context: LambdaContext) -> dict: diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 9cb833a161d..54a389dd6b8 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -1,5 +1,6 @@ import asyncio import sys +from typing import Optional import pytest @@ -199,22 +200,23 @@ def test_resolve_batch_processing(): "fieldName": "listLocations", "arguments": {}, "source": { - "id": "3", + "id": [3, 4], }, }, ] app = AppSyncResolver() - @app.resolver(field_name="listLocations") - def create_something(): # noqa AA03 VNE003 - return [event.source["id"] for event in app.current_event] + @app.batch_resolver(field_name="listLocations") + def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + return event.source["id"] if event.source else None # Call the implicit handler result = app.resolve(event, LambdaContext()) - assert result == ["1", "2", "3"] + assert result == [appsync_event["source"]["id"] for appsync_event in event] - assert len(app.current_event) == len(event) + assert app.current_batch_event and len(app.current_batch_event) == len(event) + assert not app.current_event def test_resolver_include_resolver(): From b7a439132b1ea068188f6592f7c91c8777a9df04 Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 31 Mar 2023 12:27:22 +0200 Subject: [PATCH 08/75] Refactor code to use composition instead of inheritence --- .../event_handler/appsync.py | 200 +++++++++++------- .../split_operation_append_context_module.py | 2 +- .../functional/event_handler/test_appsync.py | 112 +++++++++- 3 files changed, 228 insertions(+), 86 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 7ed80f68804..41fc31b9b30 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,6 +1,7 @@ import logging +from abc import ABC, abstractmethod from itertools import groupby -from typing import Any, Callable, List, Optional, Type, Union +from typing import Any, Callable, Dict, List, Optional, Type, Union from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -10,23 +11,68 @@ class RouterContext: def __init__(self): - super().__init__() - self.context = {} + self._context = {} - def append_context(self, **additional_context): + @property + def context(self) -> Dict[str, Any]: + return self._context + + @context.setter + def context(self, additional_context: Dict[str, Any]) -> None: """Append key=value data as routing context""" - self.context.update(**additional_context) + self._context.update(**additional_context) - def clear_context(self): + @context.deleter + def context(self): """Resets routing context""" - self.context.clear() + self._context.clear() + + +class IResolverRegistry(ABC): + @property + @abstractmethod + def resolvers(self) -> Dict[str, Dict[str, Any]]: + ... + + @resolvers.setter + @abstractmethod + def resolvers(self, resolvers: dict) -> None: + ... + + @abstractmethod + def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + ... + + @abstractmethod + def find_resolver(self, type_name: str, field_name: str) -> Callable: + ... + + +class IPublic(ABC): + @abstractmethod + def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + ... + + @abstractmethod + def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + ... + @abstractmethod + def append_context(self, **additional_context) -> None: + ... -class ResolverRegistry: + +class ResolverRegistry(IResolverRegistry): def __init__(self): - super().__init__() - self._resolvers: dict = {} - self._batch_resolvers: dict = {} + self._resolvers: Dict[str, Dict[str, Any]] = {} + + @property + def resolvers(self) -> Dict[str, Dict[str, Any]]: + return self._resolvers + + @resolvers.setter + def resolvers(self, resolvers: dict) -> None: + self._resolvers.update(resolvers) def resolver(self, type_name: str = "*", field_name: Optional[str] = None): """Registers the resolver for field_name @@ -46,8 +92,8 @@ def register(func): return register - def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None): - """Registers the resolver for field_name + def find_resolver(self, type_name: str, field_name: str) -> Callable: + """Find resolver based on type_name and field_name Parameters ---------- @@ -57,15 +103,14 @@ def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) Field name """ - def register(func): - logger.debug(f"Adding batch resolver `{func.__name__}` for field `{type_name}.{field_name}`") - self._batch_resolvers[f"{type_name}.{field_name}"] = {"func": func} - return func - - return register + full_name = f"{type_name}.{field_name}" + resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}")) + if not resolver: + raise ValueError(f"No resolver found for '{full_name}'") + return resolver["func"] -class AppSyncResolver(ResolverRegistry, RouterContext): +class AppSyncResolver(IPublic): """ AppSync resolver decorator @@ -97,22 +142,25 @@ def common_field() -> str: """ def __init__(self): - super().__init__() + self._resolver_registry: IResolverRegistry = ResolverRegistry() + self._batch_resolver_registry: IResolverRegistry = ResolverRegistry() + self._router_context: RouterContext = RouterContext() self.current_batch_event: List[AppSyncResolverEvent] = [] self.current_event: Optional[AppSyncResolverEvent] = None + self.lambda_context: Optional[LambdaContext] = None def resolve( self, - event: Union[dict, List[dict]], + event: Union[Dict[str, Any], List[Dict[str, Any]]], context: LambdaContext, data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, ) -> Any: - """Resolve field_name + """Resolve field_name in single event or in a batch event Parameters ---------- - event : dict | List[dict] - Lambda event + event : dict | List[Dict] + Lambda event either coming from batch processing endpoint or from standard processing endpoint context : LambdaContext Lambda context data_model: @@ -180,17 +228,26 @@ def lambda_handler(event, context): self.lambda_context = context response = ( - self._call_batch_resolver(event, data_model) + self._call_batch_resolver(event=event, data_model=data_model) if isinstance(event, list) - else self._call_resolver(event, data_model) + else self._call_single_resolver(event=event, data_model=data_model) ) - self.clear_context() + del self._router_context.context return response - def _call_resolver(self, event: dict, data_model: Type[AppSyncResolverEvent]) -> Any: + def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEvent]) -> Any: + """Call single event resolver + + Parameters + ---------- + event : dict + Type name + data_model : Type[AppSyncResolverEvent] + Data_model to decode AppSync event, by default it is of AppSyncResolverEvent type or subclass of it + """ self.current_event = data_model(event) - resolver = self._get_resolver(self.current_event.type_name, self.current_event.field_name) + resolver = self._resolver_registry.find_resolver(self.current_event.type_name, self.current_event.field_name) return resolver(**self.current_event.arguments) def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolverEvent]) -> List[Any]: @@ -202,53 +259,14 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv ValueError("batch with different field names. It shouldn't happen!") self.current_batch_event = [data_model(event) for event in event_groups[0]["events"]] - resolver = self._get_batch_resolver( + resolver = self._batch_resolver_registry.find_resolver( self.current_batch_event[0].type_name, self.current_batch_event[0].field_name ) - return [resolver(event=appconfig_event) for appconfig_event in self.current_batch_event] - - def _get_resolver(self, type_name: str, field_name: str) -> Callable: - """Get resolver for field_name - - Parameters - ---------- - type_name : str - Type name - field_name : str - Field name - - Returns - ------- - Callable - callable function and configuration - """ - full_name = f"{type_name}.{field_name}" - resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}")) - if not resolver: - raise ValueError(f"No resolver found for '{full_name}'") - return resolver["func"] - - def _get_batch_resolver(self, type_name: str, field_name: str) -> Callable: - """Get resolver for field_name - - Parameters - ---------- - type_name : str - Type name - field_name : str - Field name - - Returns - ------- - Callable - callable function and configuration - """ - full_name = f"{type_name}.{field_name}" - resolver = self._batch_resolvers.get(full_name, self._batch_resolvers.get(f"*.{field_name}")) - if not resolver: - raise ValueError(f"No batch resolver found for '{full_name}'") - return resolver["func"] + return [ + resolver(event=appconfig_event, **appconfig_event.arguments) + for appconfig_event in self.current_batch_event + ] def __call__( self, @@ -267,14 +285,38 @@ def include_router(self, router: "Router") -> None: router : Router A router containing a dict of field resolvers """ + # Merge app and router context - self.context.update(**router.context) + self._router_context.context = router._router_context.context # use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx) - router.context = self.context + router._router_context._context = self._router_context.context + + self._resolver_registry.resolvers = router._resolver_registry.resolvers + self._batch_resolver_registry.resolvers = router._batch_resolver_registry.resolvers + + # Interfaces + def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) - self._resolvers.update(router._resolvers) + def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def append_context(self, **additional_context) -> None: + self._router_context.context = additional_context -class Router(RouterContext, ResolverRegistry): + +class Router(IPublic): def __init__(self): - super().__init__() + self._resolver_registry: IResolverRegistry = ResolverRegistry() + self._batch_resolver_registry: IResolverRegistry = ResolverRegistry() + self._router_context: RouterContext = RouterContext() + + # Interfaces + def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) + + def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) + + def append_context(self, **additional_context) -> None: + self._router_context.context = additional_context diff --git a/examples/event_handler_graphql/src/split_operation_append_context_module.py b/examples/event_handler_graphql/src/split_operation_append_context_module.py index e30e345c313..1df44c7945b 100644 --- a/examples/event_handler_graphql/src/split_operation_append_context_module.py +++ b/examples/event_handler_graphql/src/split_operation_append_context_module.py @@ -26,5 +26,5 @@ class Location(TypedDict, total=False): @router.resolver(field_name="locations") @tracer.capture_method def get_locations(name: str, description: str = "") -> List[Location]: # match GraphQL Query arguments - is_admin: bool = router.context.get("is_admin", False) + is_admin: bool = router._router_context.context.get("is_admin", False) return [{"name": name, "description": description}] if is_admin else [] diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 54a389dd6b8..57c48fd0379 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -245,16 +245,116 @@ def get_locations2(name: str): assert result2 == "get_locations2#value" +def test_resolver_include_batch_resolver(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.batch_resolver(type_name="Query", field_name="listLocations") + def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + @app.batch_resolver(field_name="listLocations2") + def get_locations2(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations2#" + name + "#" + event.source["id"] + + app.include_router(router) + + # WHEN + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + } + ] + mock_event2 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations2", + "parentTypeName": "Post", + }, + "fieldName": "listLocations2", + "arguments": {"name": "value"}, + "source": { + "id": "2", + }, + } + ] + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == ["get_locations#value#1"] + assert result2 == ["get_locations2#value#2"] + + +def test_resolver_include_mixed_resolver(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.batch_resolver(type_name="Query", field_name="listLocations") + def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + @app.resolver(field_name="listLocations2") + def get_locations2(name: str) -> str: + return "get_locations2#" + name + + app.include_router(router) + + # WHEN + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + } + ] + mock_event2 = { + "typeName": "Query", + "info": { + "fieldName": "listLocations2", + "parentTypeName": "Post", + }, + "fieldName": "listLocations2", + "arguments": {"name": "value"}, + } + + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == ["get_locations#value#1"] + assert result2 == "get_locations2#value" + + def test_append_context(): app = AppSyncResolver() app.append_context(is_admin=True) - assert app.context.get("is_admin") is True + assert app._router_context.context.get("is_admin") is True def test_router_append_context(): router = Router() router.append_context(is_admin=True) - assert router.context.get("is_admin") is True + assert router._router_context.context.get("is_admin") is True def test_route_context_is_cleared_after_resolve(): @@ -271,7 +371,7 @@ def get_locations(name: str): app.resolve(event, {}) # THEN context should be empty - assert app.context == {} + assert app._router_context.context == {} def test_router_has_access_to_app_context(): @@ -282,7 +382,7 @@ def test_router_has_access_to_app_context(): @router.resolver(type_name="Query", field_name="listLocations") def get_locations(name: str): - if router.context["is_admin"]: + if router._router_context.context.get("is_admin"): return f"get_locations#{name}" app.include_router(router) @@ -293,7 +393,7 @@ def get_locations(name: str): # THEN assert ret == "get_locations#value" - assert router.context == {} + assert router._router_context.context == {} def test_include_router_merges_context(): @@ -307,4 +407,4 @@ def test_include_router_merges_context(): app.include_router(router) - assert app.context == router.context + assert app._router_context.context == router._router_context.context From e1d2caa8817d9e1f8bb1fb25385369d7fcf4b658 Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 7 Apr 2023 13:12:34 +0200 Subject: [PATCH 09/75] Refactor appsync event handler --- .../event_handler/appsync.py | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 41fc31b9b30..710663b10cc 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,6 +1,5 @@ import logging from abc import ABC, abstractmethod -from itertools import groupby from typing import Any, Callable, Dict, List, Optional, Type, Union from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent @@ -28,7 +27,7 @@ def context(self): self._context.clear() -class IResolverRegistry(ABC): +class BaseResolverRegistry(ABC): @property @abstractmethod def resolvers(self) -> Dict[str, Dict[str, Any]]: @@ -48,7 +47,7 @@ def find_resolver(self, type_name: str, field_name: str) -> Callable: ... -class IPublic(ABC): +class BasePublic(ABC): @abstractmethod def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: ... @@ -62,7 +61,7 @@ def append_context(self, **additional_context) -> None: ... -class ResolverRegistry(IResolverRegistry): +class ResolverRegistry(BaseResolverRegistry): def __init__(self): self._resolvers: Dict[str, Dict[str, Any]] = {} @@ -110,7 +109,24 @@ def find_resolver(self, type_name: str, field_name: str) -> Callable: return resolver["func"] -class AppSyncResolver(IPublic): +class Router(BasePublic): + def __init__(self): + self._resolver_registry: BaseResolverRegistry = ResolverRegistry() + self._batch_resolver_registry: BaseResolverRegistry = ResolverRegistry() + self._router_context: RouterContext = RouterContext() + + # Interfaces + def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) + + def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) + + def append_context(self, **additional_context) -> None: + self._router_context.context = additional_context + + +class AppSyncResolver(Router): """ AppSync resolver decorator @@ -142,9 +158,7 @@ def common_field() -> str: """ def __init__(self): - self._resolver_registry: IResolverRegistry = ResolverRegistry() - self._batch_resolver_registry: IResolverRegistry = ResolverRegistry() - self._router_context: RouterContext = RouterContext() + super().__init__() self.current_batch_event: List[AppSyncResolverEvent] = [] self.current_event: Optional[AppSyncResolverEvent] = None self.lambda_context: Optional[LambdaContext] = None @@ -242,23 +256,32 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve Parameters ---------- event : dict - Type name + Event data_model : Type[AppSyncResolverEvent] Data_model to decode AppSync event, by default it is of AppSyncResolverEvent type or subclass of it """ + self.current_event = data_model(event) resolver = self._resolver_registry.find_resolver(self.current_event.type_name, self.current_event.field_name) return resolver(**self.current_event.arguments) def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolverEvent]) -> List[Any]: - event_groups = [ - {"field_name": field_name, "events": list(events)} - for field_name, events in groupby(event, key=lambda x: x["info"]["fieldName"]) - ] - if len(event_groups) > 1: + """Call batch event resolver + + Parameters + ---------- + event : List[dict] + Event + data_model : Type[AppSyncResolverEvent] + Data_model to decode AppSync event, by default it is of AppSyncResolverEvent type or subclass of it + """ + + # Check if all events have the same field name + if len({x["info"]["fieldName"] for x in event}) > 1: ValueError("batch with different field names. It shouldn't happen!") - self.current_batch_event = [data_model(event) for event in event_groups[0]["events"]] + self.current_batch_event = [data_model(e) for e in event] + resolver = self._batch_resolver_registry.find_resolver( self.current_batch_event[0].type_name, self.current_batch_event[0].field_name ) @@ -303,20 +326,3 @@ def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) def append_context(self, **additional_context) -> None: self._router_context.context = additional_context - - -class Router(IPublic): - def __init__(self): - self._resolver_registry: IResolverRegistry = ResolverRegistry() - self._batch_resolver_registry: IResolverRegistry = ResolverRegistry() - self._router_context: RouterContext = RouterContext() - - # Interfaces - def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) - - def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) - - def append_context(self, **additional_context) -> None: - self._router_context.context = additional_context From 8fe6a8b1f6685555f145eba9a7fc513ac22dadba Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 27 Apr 2023 01:17:17 +0100 Subject: [PATCH 10/75] fix style --- aws_lambda_powertools/event_handler/appsync.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 710663b10cc..d28f4bf63aa 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -287,8 +287,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv ) return [ - resolver(event=appconfig_event, **appconfig_event.arguments) - for appconfig_event in self.current_batch_event + resolver(event=appconfig_event, **appconfig_event.arguments) for appconfig_event in self.current_batch_event ] def __call__( From d5bbd095b86909453746c14b15b9bc354e0c99fb Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Fri, 21 Jul 2023 21:30:56 +0200 Subject: [PATCH 11/75] Add support for async batch processing --- .../event_handler/appsync.py | 58 +++-- tests/e2e/event_handler/files/schema.graphql | 1 + .../handlers/appsync_resolver_handler.py | 5 + tests/e2e/event_handler/infrastructure.py | 14 +- .../event_handler/test_appsync_resolvers.py | 12 +- .../functional/event_handler/test_appsync.py | 203 +++++++++++++++++- 6 files changed, 272 insertions(+), 21 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 4398e2cfcd2..24548ae09a9 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,3 +1,4 @@ +import asyncio import logging from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List, Optional, Type, Union @@ -43,7 +44,7 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Ca ... @abstractmethod - def find_resolver(self, type_name: str, field_name: str) -> Callable: + def find_resolver(self, type_name: str, field_name: str) -> Optional[Callable]: ... @@ -91,7 +92,7 @@ def register(func): return register - def find_resolver(self, type_name: str, field_name: str) -> Callable: + def find_resolver(self, type_name: str, field_name: str) -> Optional[Callable]: """Find resolver based on type_name and field_name Parameters @@ -102,10 +103,9 @@ def find_resolver(self, type_name: str, field_name: str) -> Callable: Field name """ - full_name = f"{type_name}.{field_name}" - resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}")) + resolver = self._resolvers.get(f"{type_name}.{field_name}", self._resolvers.get(f"*.{field_name}")) if not resolver: - raise ValueError(f"No resolver found for '{full_name}'") + return None return resolver["func"] @@ -113,6 +113,7 @@ class Router(BasePublic): def __init__(self): self._resolver_registry: BaseResolverRegistry = ResolverRegistry() self._batch_resolver_registry: BaseResolverRegistry = ResolverRegistry() + self._batch_async_resolver_registry: BaseResolverRegistry = ResolverRegistry() self._router_context: RouterContext = RouterContext() # Interfaces @@ -122,6 +123,9 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Ca def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._batch_async_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def append_context(self, **additional_context) -> None: self._router_context.context = additional_context @@ -165,7 +169,7 @@ def __init__(self): def resolve( self, - event: dict, + event: Union[dict, List[Dict]], context: LambdaContext, data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, ) -> Any: @@ -263,10 +267,25 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve self.current_event = data_model(event) resolver = self._resolver_registry.find_resolver(self.current_event.type_name, self.current_event.field_name) + if not resolver: + raise ValueError(f"No resolver found for '{self.current_event.type_name}.{self.current_event.field_name}'") return resolver(**self.current_event.arguments) + def _call_sync_batch_resolver(self, sync_resolver: Callable) -> List[Any]: + return [ + sync_resolver(event=appconfig_event, **appconfig_event.arguments) + for appconfig_event in self.current_batch_event + ] + + async def _call_async_batch_resolver(self, async_resolver: Callable) -> List[Any]: + tasks = [ + asyncio.ensure_future(async_resolver(event=appconfig_event, **appconfig_event.arguments)) + for appconfig_event in self.current_batch_event + ] + return await asyncio.gather(*tasks) + def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolverEvent]) -> List[Any]: - """Call batch event resolver + """Call batch event resolver for sync and async methods Parameters ---------- @@ -282,13 +301,24 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv self.current_batch_event = [data_model(e) for e in event] + # Check if we have synchronous or asynchronous resolver available resolver = self._batch_resolver_registry.find_resolver( - self.current_batch_event[0].type_name, self.current_batch_event[0].field_name + self.current_batch_event[0].type_name, + self.current_batch_event[0].field_name, ) - - return [ - resolver(event=appconfig_event, **appconfig_event.arguments) for appconfig_event in self.current_batch_event - ] + async_resolver = self._batch_async_resolver_registry.find_resolver( + self.current_batch_event[0].type_name, + self.current_batch_event[0].field_name, + ) + if resolver: + return self._call_sync_batch_resolver(resolver) + elif async_resolver: + return asyncio.run(self._call_async_batch_resolver(async_resolver)) + else: + raise ValueError( + f"No resolver found for \ + '{self.current_batch_event[0].type_name}.{self.current_batch_event[0].field_name}'", + ) def __call__( self, @@ -315,6 +345,7 @@ def include_router(self, router: "Router") -> None: self._resolver_registry.resolvers = router._resolver_registry.resolvers self._batch_resolver_registry.resolvers = router._batch_resolver_registry.resolvers + self._batch_async_resolver_registry.resolvers = router._batch_async_resolver_registry.resolvers # Interfaces def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: @@ -323,5 +354,8 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Ca def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._batch_async_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def append_context(self, **additional_context) -> None: self._router_context.context = additional_context diff --git a/tests/e2e/event_handler/files/schema.graphql b/tests/e2e/event_handler/files/schema.graphql index 0558f161ed5..abc2f93562b 100644 --- a/tests/e2e/event_handler/files/schema.graphql +++ b/tests/e2e/event_handler/files/schema.graphql @@ -16,4 +16,5 @@ type Post { ups: Int downs: Int relatedPosts: [Post] + relatedPostsAsync: [Post] } \ No newline at end of file diff --git a/tests/e2e/event_handler/handlers/appsync_resolver_handler.py b/tests/e2e/event_handler/handlers/appsync_resolver_handler.py index c904a86e490..60febd49640 100644 --- a/tests/e2e/event_handler/handlers/appsync_resolver_handler.py +++ b/tests/e2e/event_handler/handlers/appsync_resolver_handler.py @@ -92,5 +92,10 @@ def related_posts(event: AppSyncResolverEvent) -> Optional[list]: return posts_related[event.source["post_id"]] if event.source else None +@app.batch_async_resolver(type_name="Post", field_name="relatedPostsAsync") +async def related_posts_async(event: AppSyncResolverEvent) -> Optional[list]: + return posts_related[event.source["post_id"]] if event.source else None + + def lambda_handler(event, context: LambdaContext) -> dict: return app.resolve(event, context) diff --git a/tests/e2e/event_handler/infrastructure.py b/tests/e2e/event_handler/infrastructure.py index bcd61d077c5..188ef0a4bd1 100644 --- a/tests/e2e/event_handler/infrastructure.py +++ b/tests/e2e/event_handler/infrastructure.py @@ -109,7 +109,7 @@ def _create_appsync_endpoint(self, function: Function): expires=Expiration.after(Duration.hours(25)), name="API Token", ), - ) + ), ), xray_enabled=False, ) @@ -126,7 +126,17 @@ def _create_appsync_endpoint(self, function: Function): field_name="getPost", ) lambda_datasource.create_resolver( - "QueryGetPostRelatedResolver", type_name="Post", field_name="relatedPosts", max_batch_size=10 + "QueryGetPostRelatedResolver", + type_name="Post", + field_name="relatedPosts", + max_batch_size=10, + ) + + lambda_datasource.create_resolver( + "QueryGetPostRelatedAsyncResolver", + type_name="Post", + field_name="relatedPostsAsync", + max_batch_size=10, ) CfnOutput(self.stack, "GraphQLHTTPUrl", value=api.graphql_url) diff --git a/tests/e2e/event_handler/test_appsync_resolvers.py b/tests/e2e/event_handler/test_appsync_resolvers.py index 2e20959ec43..469c6d4946a 100644 --- a/tests/e2e/event_handler/test_appsync_resolvers.py +++ b/tests/e2e/event_handler/test_appsync_resolvers.py @@ -32,7 +32,7 @@ def test_appsync_get_all_posts(appsync_endpoint, appsync_access_key): url=appsync_endpoint, json=body, headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, - ) + ), ) # THEN expect a HTTP 200 response and content return list of Posts @@ -62,7 +62,7 @@ def test_appsync_get_post(appsync_endpoint, appsync_access_key): url=appsync_endpoint, json=body, headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, - ) + ), ) # THEN expect a HTTP 200 response and content return Post id @@ -81,7 +81,8 @@ def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): related_posts_ids = ["3", "5"] body = { - "query": f'query MyQuery {{ getPost(post_id: "{post_id}") {{ post_id relatedPosts {{ post_id }} }} }}', + "query": f'query MyQuery {{ getPost(post_id: "{post_id}") \ + {{ post_id relatedPosts {{ post_id }} relatedPostsAsync {{ post_id }} }} }}', "variables": None, "operationName": "MyQuery", } @@ -93,7 +94,7 @@ def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): url=appsync_endpoint, json=body, headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, - ) + ), ) # THEN expect a HTTP 200 response and content return Post id with dependent Posts id's @@ -104,5 +105,8 @@ def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): assert data["getPost"]["post_id"] == post_id assert len(data["getPost"]["relatedPosts"]) == len(related_posts_ids) + assert len(data["getPost"]["relatedPostsAsync"]) == len(related_posts_ids) for post in data["getPost"]["relatedPosts"]: assert post["post_id"] in related_posts_ids + for post in data["getPost"]["relatedPostsAsync"]: + assert post["post_id"] in related_posts_ids diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 57c48fd0379..8d5ec48a7d3 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -273,7 +273,7 @@ def get_locations2(event: AppSyncResolverEvent, name: str) -> str: "source": { "id": "1", }, - } + }, ] mock_event2 = [ { @@ -287,7 +287,7 @@ def get_locations2(event: AppSyncResolverEvent, name: str) -> str: "source": { "id": "2", }, - } + }, ] result1 = app.resolve(mock_event1, LambdaContext()) result2 = app.resolve(mock_event2, LambdaContext()) @@ -325,7 +325,7 @@ def get_locations2(name: str) -> str: "source": { "id": "1", }, - } + }, ] mock_event2 = { "typeName": "Query", @@ -345,6 +345,203 @@ def get_locations2(name: str) -> str: assert result2 == "get_locations2#value" +def test_resolve_async_batch_processing(): + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": [3, 4], + }, + }, + ] + + app = AppSyncResolver() + + @app.batch_async_resolver(field_name="listLocations") + async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + return event.source["id"] if event.source else None + + # Call the implicit handler + result = app.resolve(event, LambdaContext()) + assert result == [appsync_event["source"]["id"] for appsync_event in event] + + assert app.current_batch_event and len(app.current_batch_event) == len(event) + + +def test_resolve_async_and_sync_batch_processing(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.batch_async_resolver(type_name="Query", field_name="listLocations") + async def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + @app.batch_resolver(field_name="listLocations2") + def get_locations2(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations2#" + name + "#" + event.source["id"] + + app.include_router(router) + + # WHEN + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + }, + ] + mock_event2 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations2", + "parentTypeName": "Post", + }, + "fieldName": "listLocations2", + "arguments": {"name": "value"}, + "source": { + "id": "2", + }, + }, + ] + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == ["get_locations#value#1"] + assert result2 == ["get_locations2#value#2"] + + +def test_resolve_async_batch_and_sync_singular_processing(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.batch_async_resolver(type_name="Query", field_name="listLocations") + async def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + @app.resolver(type_name="Query", field_name="listLocation") + def get_location(name: str) -> str: + return "get_location#" + name + + app.include_router(router) + + # WHEN + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + }, + ] + mock_event2 = {"typeName": "Query", "fieldName": "listLocation", "arguments": {"name": "value"}} + + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == ["get_locations#value#1"] + assert result2 == "get_location#value" + + +def test_async_resolver_include_batch_resolver(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.batch_async_resolver(type_name="Query", field_name="listLocations") + async def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + @app.batch_async_resolver(field_name="listLocations2") + async def get_locations2(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations2#" + name + "#" + event.source["id"] + + app.include_router(router) + + # WHEN + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + }, + ] + mock_event2 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations2", + "parentTypeName": "Post", + }, + "fieldName": "listLocations2", + "arguments": {"name": "value"}, + "source": { + "id": "2", + }, + }, + ] + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == ["get_locations#value#1"] + assert result2 == ["get_locations2#value#2"] + + def test_append_context(): app = AppSyncResolver() app.append_context(is_admin=True) From 659a04420ece92a33c466a9f9b56b2dd5bb1fb30 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 16 Aug 2023 00:02:10 +0100 Subject: [PATCH 12/75] Fixing sonarcloud error --- aws_lambda_powertools/event_handler/appsync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 24548ae09a9..d594da2dff9 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -297,7 +297,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv # Check if all events have the same field name if len({x["info"]["fieldName"] for x in event}) > 1: - ValueError("batch with different field names. It shouldn't happen!") + raise ValueError("batch with different field names. It shouldn't happen!") self.current_batch_event = [data_model(e) for e in event] From 13b0bcd436bd10bbd343195a7e8ac70aa142576c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 16 Aug 2023 00:35:20 +0100 Subject: [PATCH 13/75] Adding docstring + increasing coverage --- .../event_handler/appsync.py | 112 ++++++++++++++++-- .../functional/event_handler/test_appsync.py | 43 +++++++ 2 files changed, 148 insertions(+), 7 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index d594da2dff9..c4bcb24dbf6 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -29,37 +29,135 @@ def context(self): class BaseResolverRegistry(ABC): + """ + Abstract base class for a resolver registry. + + This class defines the interface for managing and retrieving resolvers + for various type and field combinations. + """ + @property @abstractmethod def resolvers(self) -> Dict[str, Dict[str, Any]]: - ... + """ + Get the dictionary of resolvers. + + Returns + ------- + dict + A dictionary containing resolver information. + """ + raise NotImplementedError @resolvers.setter @abstractmethod def resolvers(self, resolvers: dict) -> None: - ... + """ + Set the dictionary of resolvers. + + Parameters + ---------- + resolvers: dict + A dictionary containing resolver information. + """ + raise NotImplementedError @abstractmethod def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - ... + """ + Retrieve a resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + + Returns + ------- + Callable + The resolver function. + """ + raise NotImplementedError @abstractmethod def find_resolver(self, type_name: str, field_name: str) -> Optional[Callable]: - ... + """ + Find a resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: str + The name of the field. + + Returns + ------- + Optional[Callable] + The resolver function. None if not found. + """ + raise NotImplementedError class BasePublic(ABC): + """ + Abstract base class for public interface methods for resolver. + + This class outlines the methods that must be implemented by subclasses to manage resolvers and + context information. + """ + @abstractmethod def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - ... + """ + Retrieve a resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + + Returns + ------- + Callable + The resolver function. + """ + raise NotImplementedError @abstractmethod def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - ... + """ + Retrieve a batch resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + + Returns + ------- + Callable + The batch resolver function. + """ + raise NotImplementedError @abstractmethod def append_context(self, **additional_context) -> None: - ... + """ + Append additional context information. + + Parameters + ----------- + **additional_context: dict + Additional context key-value pairs to append. + """ + raise NotImplementedError class ResolverRegistry(BaseResolverRegistry): diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 8d5ec48a7d3..06b5b517985 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -245,6 +245,49 @@ def get_locations2(name: str): assert result2 == "get_locations2#value" +def test_resolver_batch_resolver_many_fields_with_different_name(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.batch_resolver(type_name="Query", field_name="listLocations") + def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + app.include_router(router) + + # WHEN + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocationsDifferente", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + }, + ] + + with pytest.raises(ValueError): + app.resolve(mock_event1, LambdaContext()) + + def test_resolver_include_batch_resolver(): # GIVEN app = AppSyncResolver() From db74f0ab8e4cef39f1296c9305d30820f8fbc0cb Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 16 Aug 2023 00:50:16 +0100 Subject: [PATCH 14/75] Adding missing test + increasing coverage --- .../functional/event_handler/test_appsync.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 06b5b517985..f8f71dc490b 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -273,7 +273,7 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: { "typeName": "Query", "info": { - "fieldName": "listLocationsDifferente", + "fieldName": "listLocationsDifferent", "parentTypeName": "Query", }, "fieldName": "listLocations", @@ -288,6 +288,39 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: app.resolve(mock_event1, LambdaContext()) +def test_resolver_batch_with_resolver_not_found(): + # GIVEN a AppSyncResolver + app = AppSyncResolver() + router = Router() + + # WHEN we have an event + # WHEN the event field_name doesn't match with the resolver field_name + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listCars", + "parentTypeName": "Query", + }, + "fieldName": "listCars", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + }, + ] + + @router.batch_resolver(type_name="Query", field_name="listLocations") + def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + app.include_router(router) + + # THEN must fail with ValueError + with pytest.raises(ValueError, match="No resolver found for.*"): + app.resolve(mock_event1, LambdaContext()) + + def test_resolver_include_batch_resolver(): # GIVEN app = AppSyncResolver() From 5ac0e3c811d0ed0a39c7a34ea319388ffe897f5c Mon Sep 17 00:00:00 2001 From: Michal Ploski Date: Tue, 31 Oct 2023 14:34:40 +0100 Subject: [PATCH 15/75] Start writing docs --- docs/core/event_handler/appsync.md | 16 ++++ .../src/batch_async_resolvers.py | 96 +++++++++++++++++++ .../src/batch_resolvers.py | 85 ++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 examples/event_handler_graphql/src/batch_async_resolvers.py create mode 100644 examples/event_handler_graphql/src/batch_resolvers.py diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 789bf788004..e21f4275885 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -258,7 +258,23 @@ You can use `append_context` when you want to share data between your App and Ro ```python hl_lines="29" --8<-- "examples/event_handler_graphql/src/split_operation_append_context_module.py" ``` +### Batch processing +We also support Appsync batching mechanism for Lambda Resolvers. In case you want to handle multiple events in a batch to avoid multiple lambda executions just configure your Appsync to group multiple events together and then annotate your function with `@batch_resolver` decorator. Batch resolver will execute your function with every event in the provided batch list. +???+ info + If you want to understand more how to configure batch processing for the appsynch, please follow this [guide](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"} + +=== "batch_resolvers.py" + ```python hl_lines="79-81" + --8<-- "examples/event_handler_graphql/src/batch_resolvers.py" + ``` + +#### Async +Alternatively, you can use async batch processor. Use it if you want to benefit from concurrency and keeping the order of execution is not required for you. +=== "batch_async_resolvers.py" + ```python hl_lines="90-92" + --8<-- "examples/event_handler_graphql/src/batch_async_resolvers.py" + ``` ## Testing your code You can test your resolvers by passing a mocked or actual AppSync Lambda event that you're expecting. diff --git a/examples/event_handler_graphql/src/batch_async_resolvers.py b/examples/event_handler_graphql/src/batch_async_resolvers.py new file mode 100644 index 00000000000..cc8cab05178 --- /dev/null +++ b/examples/event_handler_graphql/src/batch_async_resolvers.py @@ -0,0 +1,96 @@ +from typing import List, Optional + +from pydantic import BaseModel + +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = AppSyncResolver() + + +posts = { + "1": { + "post_id": "1", + "title": "First book", + "author": "Author1", + "url": "https://amazon.com/", + "content": "SAMPLE TEXT AUTHOR 1", + "ups": "100", + "downs": "10", + }, + "2": { + "post_id": "2", + "title": "Second book", + "author": "Author2", + "url": "https://amazon.com", + "content": "SAMPLE TEXT AUTHOR 2", + "ups": "100", + "downs": "10", + }, + "3": { + "post_id": "3", + "title": "Third book", + "author": "Author3", + "url": None, + "content": None, + "ups": None, + "downs": None, + }, + "4": { + "post_id": "4", + "title": "Fourth book", + "author": "Author4", + "url": "https://www.amazon.com/", + "content": "SAMPLE TEXT AUTHOR 4", + "ups": "1000", + "downs": "0", + }, + "5": { + "post_id": "5", + "title": "Fifth book", + "author": "Author5", + "url": "https://www.amazon.com/", + "content": "SAMPLE TEXT AUTHOR 5", + "ups": "50", + "downs": "0", + }, +} + +posts_related = { + "1": [posts["4"]], + "2": [posts["3"], posts["5"]], + "3": [posts["2"], posts["1"]], + "4": [posts["2"], posts["1"]], + "5": [], +} + + +class Post(BaseModel): + post_id: str + author: str + title: str + url: str + content: str + ups: str + downs: str + + +@app.resolver(type_name="Query", field_name="getPost") +def get_post(post_id: str = "") -> dict: + post = Post(**posts[post_id]).dict() + return post + + +@app.resolver(type_name="Query", field_name="allPosts") +def all_posts() -> List[dict]: + return list(posts.values()) + + +@app.batch_async_resolver(type_name="Post", field_name="relatedPosts") +async def related_posts_async(event: AppSyncResolverEvent) -> Optional[list]: + return posts_related[event.source["post_id"]] if event.source else None + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/batch_resolvers.py b/examples/event_handler_graphql/src/batch_resolvers.py new file mode 100644 index 00000000000..e63eb7e3d85 --- /dev/null +++ b/examples/event_handler_graphql/src/batch_resolvers.py @@ -0,0 +1,85 @@ +from typing import List, Optional + +from pydantic import BaseModel + +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = AppSyncResolver() + + +posts = { + "1": { + "post_id": "1", + "title": "First book", + "author": "Author1", + "url": "https://amazon.com/", + "content": "SAMPLE TEXT AUTHOR 1", + "ups": "100", + "downs": "10", + }, + "2": { + "post_id": "2", + "title": "Second book", + "author": "Author2", + "url": "https://amazon.com", + "content": "SAMPLE TEXT AUTHOR 2", + "ups": "100", + "downs": "10", + }, + "3": { + "post_id": "3", + "title": "Third book", + "author": "Author3", + "url": None, + "content": None, + "ups": None, + "downs": None, + }, + "4": { + "post_id": "4", + "title": "Fourth book", + "author": "Author4", + "url": "https://www.amazon.com/", + "content": "SAMPLE TEXT AUTHOR 4", + "ups": "1000", + "downs": "0", + }, + "5": { + "post_id": "5", + "title": "Fifth book", + "author": "Author5", + "url": "https://www.amazon.com/", + "content": "SAMPLE TEXT AUTHOR 5", + "ups": "50", + "downs": "0", + }, +} + +posts_related = { + "1": [posts["4"]], + "2": [posts["3"], posts["5"]], + "3": [posts["2"], posts["1"]], + "4": [posts["2"], posts["1"]], + "5": [], +} + + +class Post(BaseModel): + post_id: str + author: str + title: str + url: str + content: str + ups: str + downs: str + + +@app.batch_resolver(type_name="Post", field_name="relatedPosts") +def related_posts(event: AppSyncResolverEvent) -> Optional[list]: + return posts_related[event.source["post_id"]] if event.source else None + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) From 5f44a2b47f2e9801a89f970296d82257bf61e57d Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 23 Jan 2024 17:15:56 +0000 Subject: [PATCH 16/75] Refactoring examples --- docs/core/event_handler/appsync.md | 4 ++++ examples/event_handler_graphql/src/batch_resolvers.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index e21f4275885..8297d93de2e 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -258,7 +258,9 @@ You can use `append_context` when you want to share data between your App and Ro ```python hl_lines="29" --8<-- "examples/event_handler_graphql/src/split_operation_append_context_module.py" ``` + ### Batch processing + We also support Appsync batching mechanism for Lambda Resolvers. In case you want to handle multiple events in a batch to avoid multiple lambda executions just configure your Appsync to group multiple events together and then annotate your function with `@batch_resolver` decorator. Batch resolver will execute your function with every event in the provided batch list. ???+ info @@ -270,11 +272,13 @@ We also support Appsync batching mechanism for Lambda Resolvers. In case you wan ``` #### Async + Alternatively, you can use async batch processor. Use it if you want to benefit from concurrency and keeping the order of execution is not required for you. === "batch_async_resolvers.py" ```python hl_lines="90-92" --8<-- "examples/event_handler_graphql/src/batch_async_resolvers.py" ``` + ## Testing your code You can test your resolvers by passing a mocked or actual AppSync Lambda event that you're expecting. diff --git a/examples/event_handler_graphql/src/batch_resolvers.py b/examples/event_handler_graphql/src/batch_resolvers.py index e63eb7e3d85..b33cdb9f50b 100644 --- a/examples/event_handler_graphql/src/batch_resolvers.py +++ b/examples/event_handler_graphql/src/batch_resolvers.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from pydantic import BaseModel From ab7d5b9f02dcd6ddc15e40cc8a1c569172f8f59d Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 25 Jan 2024 15:28:17 +0000 Subject: [PATCH 17/75] Refactoring code + examples + documentation --- .../event_handler/appsync.py | 123 +++++- .../event_handler/exceptions_appsync.py | 10 + docs/core/event_handler/appsync.md | 30 +- .../src/batch_async_resolvers.py | 96 ----- .../src/batch_resolvers.py | 85 ---- ...tting_started_with_batch_async_resolver.py | 23 ++ ...ted_with_batch_async_resolver_payload.json | 52 +++ .../getting_started_with_batch_resolver.py | 23 ++ ...g_started_with_batch_resolver_payload.json | 52 +++ .../functional/event_handler/test_appsync.py | 381 +++++++++--------- 10 files changed, 474 insertions(+), 401 deletions(-) create mode 100644 aws_lambda_powertools/event_handler/exceptions_appsync.py delete mode 100644 examples/event_handler_graphql/src/batch_async_resolvers.py delete mode 100644 examples/event_handler_graphql/src/batch_resolvers.py create mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py create mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json create mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_resolver.py create mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index c4bcb24dbf6..1c2c53cfde7 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,8 +1,10 @@ import asyncio import logging +import warnings from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List, Optional, Type, Union +from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayload, ResolverNotFound from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -121,6 +123,25 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Ca field_name: Optional[str] The name of the field (default is None). + Examples + -------- + ``` + from typing import Optional + + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext + + app = AppSyncResolver() + + @app.resolver(type_name="Query", field_name="getPost") + def related_posts(event: AppSyncResolverEvent) -> Optional[list]: + return {"success": "ok"} + + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) + ``` + Returns ------- Callable @@ -140,6 +161,63 @@ def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) field_name: Optional[str] The name of the field (default is None). + Examples + -------- + ``` + from typing import Optional + + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext + + app = AppSyncResolver() + + @app.batch_resolver(type_name="Query", field_name="getPost") + def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: + return {"post_id": id} + + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) + ``` + + Returns + ------- + Callable + The batch resolver function. + """ + raise NotImplementedError + + @abstractmethod + def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + """ + Retrieve a batch resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + + Examples + -------- + ``` + from typing import Optional + + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext + + app = AppSyncResolver() + + @app.batch_async_resolver(type_name="Query", field_name="getPost") + async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: + return {"post_id": id} + + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) + ``` + Returns ------- Callable @@ -391,32 +469,45 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv Event data_model : Type[AppSyncResolverEvent] Data_model to decode AppSync event, by default it is of AppSyncResolverEvent type or subclass of it + + Returns + ------- + List[Any] + Results of the resolver execution. + + Raises + ------ + InconsistentPayload: + If all events in the batch do not have the same fieldName. + + ResolverNotFound: + If no resolver is found for the specified type and field. """ - # Check if all events have the same field name - if len({x["info"]["fieldName"] for x in event}) > 1: - raise ValueError("batch with different field names. It shouldn't happen!") + # All events in the batch must have the same fieldName + field_names = {field_name["info"]["fieldName"] for field_name in event} + if len(field_names) > 1: + raise InconsistentPayload(f"All events in the batch must have the same fieldName. Found: {field_names}") self.current_batch_event = [data_model(e) for e in event] + type_name, field_name = self.current_batch_event[0].type_name, self.current_batch_event[0].field_name + + resolver = self._batch_resolver_registry.find_resolver(type_name, field_name) + async_resolver = self._batch_async_resolver_registry.find_resolver(type_name, field_name) + + if resolver and async_resolver: + warnings.warn( + f"Both synchronous and asynchronous resolvers found for the same event and field." + f"The synchronous resolver takes precedence. Executing: {resolver.__name__}", + stacklevel=2, + ) - # Check if we have synchronous or asynchronous resolver available - resolver = self._batch_resolver_registry.find_resolver( - self.current_batch_event[0].type_name, - self.current_batch_event[0].field_name, - ) - async_resolver = self._batch_async_resolver_registry.find_resolver( - self.current_batch_event[0].type_name, - self.current_batch_event[0].field_name, - ) if resolver: return self._call_sync_batch_resolver(resolver) elif async_resolver: return asyncio.run(self._call_async_batch_resolver(async_resolver)) else: - raise ValueError( - f"No resolver found for \ - '{self.current_batch_event[0].type_name}.{self.current_batch_event[0].field_name}'", - ) + raise ResolverNotFound(f"No resolver found for '{type_name}.{field_name}'") def __call__( self, diff --git a/aws_lambda_powertools/event_handler/exceptions_appsync.py b/aws_lambda_powertools/event_handler/exceptions_appsync.py new file mode 100644 index 00000000000..6708a73a83f --- /dev/null +++ b/aws_lambda_powertools/event_handler/exceptions_appsync.py @@ -0,0 +1,10 @@ +class ResolverNotFound(Exception): + """ + When a resolver is not found during a lookup. + """ + + +class InconsistentPayload(Exception): + """ + When a payload is inconsistent or violates expected structure. + """ diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 8297d93de2e..5c51199978c 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -261,22 +261,36 @@ You can use `append_context` when you want to share data between your App and Ro ### Batch processing -We also support Appsync batching mechanism for Lambda Resolvers. In case you want to handle multiple events in a batch to avoid multiple lambda executions just configure your Appsync to group multiple events together and then annotate your function with `@batch_resolver` decorator. Batch resolver will execute your function with every event in the provided batch list. +We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple events in a batch and prevent multiple lambda executions, configure your Appsync to group events and use the `@batch_resolver` decorator. ???+ info If you want to understand more how to configure batch processing for the appsynch, please follow this [guide](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"} -=== "batch_resolvers.py" - ```python hl_lines="79-81" - --8<-- "examples/event_handler_graphql/src/batch_resolvers.py" +=== "getting_started_with_batch_resolver.py" + ```python hl_lines="3 7 17" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver.py" + ``` + +=== "getting_started_with_batch_resolver_payload.json" + ```json hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` #### Async -Alternatively, you can use async batch processor. Use it if you want to benefit from concurrency and keeping the order of execution is not required for you. -=== "batch_async_resolvers.py" - ```python hl_lines="90-92" - --8<-- "examples/event_handler_graphql/src/batch_async_resolvers.py" +Choose the asynchronous batch processor when your objective is to leverage concurrency capabilities without the necessity of preserving records in a specific order. + +???+ warning + Make sure that preserving the order of the response is not a requirement of your logic. + +=== "getting_started_with_batch_async_resolver.py" + ```python hl_lines="3 7 17" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py" + ``` + +=== "getting_started_with_batch_async_resolver_payload.json" + ```json hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json" ``` ## Testing your code diff --git a/examples/event_handler_graphql/src/batch_async_resolvers.py b/examples/event_handler_graphql/src/batch_async_resolvers.py deleted file mode 100644 index cc8cab05178..00000000000 --- a/examples/event_handler_graphql/src/batch_async_resolvers.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import List, Optional - -from pydantic import BaseModel - -from aws_lambda_powertools.event_handler import AppSyncResolver -from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent -from aws_lambda_powertools.utilities.typing import LambdaContext - -app = AppSyncResolver() - - -posts = { - "1": { - "post_id": "1", - "title": "First book", - "author": "Author1", - "url": "https://amazon.com/", - "content": "SAMPLE TEXT AUTHOR 1", - "ups": "100", - "downs": "10", - }, - "2": { - "post_id": "2", - "title": "Second book", - "author": "Author2", - "url": "https://amazon.com", - "content": "SAMPLE TEXT AUTHOR 2", - "ups": "100", - "downs": "10", - }, - "3": { - "post_id": "3", - "title": "Third book", - "author": "Author3", - "url": None, - "content": None, - "ups": None, - "downs": None, - }, - "4": { - "post_id": "4", - "title": "Fourth book", - "author": "Author4", - "url": "https://www.amazon.com/", - "content": "SAMPLE TEXT AUTHOR 4", - "ups": "1000", - "downs": "0", - }, - "5": { - "post_id": "5", - "title": "Fifth book", - "author": "Author5", - "url": "https://www.amazon.com/", - "content": "SAMPLE TEXT AUTHOR 5", - "ups": "50", - "downs": "0", - }, -} - -posts_related = { - "1": [posts["4"]], - "2": [posts["3"], posts["5"]], - "3": [posts["2"], posts["1"]], - "4": [posts["2"], posts["1"]], - "5": [], -} - - -class Post(BaseModel): - post_id: str - author: str - title: str - url: str - content: str - ups: str - downs: str - - -@app.resolver(type_name="Query", field_name="getPost") -def get_post(post_id: str = "") -> dict: - post = Post(**posts[post_id]).dict() - return post - - -@app.resolver(type_name="Query", field_name="allPosts") -def all_posts() -> List[dict]: - return list(posts.values()) - - -@app.batch_async_resolver(type_name="Post", field_name="relatedPosts") -async def related_posts_async(event: AppSyncResolverEvent) -> Optional[list]: - return posts_related[event.source["post_id"]] if event.source else None - - -def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/batch_resolvers.py b/examples/event_handler_graphql/src/batch_resolvers.py deleted file mode 100644 index b33cdb9f50b..00000000000 --- a/examples/event_handler_graphql/src/batch_resolvers.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - -from aws_lambda_powertools.event_handler import AppSyncResolver -from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent -from aws_lambda_powertools.utilities.typing import LambdaContext - -app = AppSyncResolver() - - -posts = { - "1": { - "post_id": "1", - "title": "First book", - "author": "Author1", - "url": "https://amazon.com/", - "content": "SAMPLE TEXT AUTHOR 1", - "ups": "100", - "downs": "10", - }, - "2": { - "post_id": "2", - "title": "Second book", - "author": "Author2", - "url": "https://amazon.com", - "content": "SAMPLE TEXT AUTHOR 2", - "ups": "100", - "downs": "10", - }, - "3": { - "post_id": "3", - "title": "Third book", - "author": "Author3", - "url": None, - "content": None, - "ups": None, - "downs": None, - }, - "4": { - "post_id": "4", - "title": "Fourth book", - "author": "Author4", - "url": "https://www.amazon.com/", - "content": "SAMPLE TEXT AUTHOR 4", - "ups": "1000", - "downs": "0", - }, - "5": { - "post_id": "5", - "title": "Fifth book", - "author": "Author5", - "url": "https://www.amazon.com/", - "content": "SAMPLE TEXT AUTHOR 5", - "ups": "50", - "downs": "0", - }, -} - -posts_related = { - "1": [posts["4"]], - "2": [posts["3"], posts["5"]], - "3": [posts["2"], posts["1"]], - "4": [posts["2"], posts["1"]], - "5": [], -} - - -class Post(BaseModel): - post_id: str - author: str - title: str - url: str - content: str - ups: str - downs: str - - -@app.batch_resolver(type_name="Post", field_name="relatedPosts") -def related_posts(event: AppSyncResolverEvent) -> Optional[list]: - return posts_related[event.source["post_id"]] if event.source else None - - -def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py new file mode 100644 index 00000000000..b155afe290e --- /dev/null +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py @@ -0,0 +1,23 @@ +from typing import Dict, Optional + +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = AppSyncResolver() + + +posts_related = { + "1": {"title": "post1"}, + "2": {"title": "post2"}, + "3": {"title": "post3"}, +} + + +@app.batch_async_resolver(type_name="Query", field_name="relatedPosts") +async def related_posts(event: AppSyncResolverEvent, post_id: str) -> Optional[Dict]: + return posts_related.get(post_id, None) + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json new file mode 100644 index 00000000000..06bbb2daaae --- /dev/null +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json @@ -0,0 +1,52 @@ +[ + { + "arguments":{ + "post_id":"1" + }, + "identity":"None", + "source":"None", + "request":{ + "headers":{ + "x-forwarded-for":"18.68.31.36", + "sec-ch-ua-mobile":"?0" + } + }, + "prev":"None", + "info":{ + "fieldName":"relatedPosts", + "selectionSetList":[ + "relatedPosts" + ], + "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", + "parentTypeName":"Query", + "variables":{ + + } + } + }, + { + "arguments":{ + "post_id":"10" + }, + "identity":"None", + "source":"None", + "request":{ + "headers":{ + "x-forwarded-for":"18.68.31.36", + "sec-ch-ua-mobile":"?0" + } + }, + "prev":"None", + "info":{ + "fieldName":"relatedPosts", + "selectionSetList":[ + "relatedPosts" + ], + "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", + "parentTypeName":"Query", + "variables":{ + + } + } + } + ] diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py new file mode 100644 index 00000000000..b3dd275cc88 --- /dev/null +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py @@ -0,0 +1,23 @@ +from typing import Dict, Optional + +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = AppSyncResolver() + + +posts_related = { + "1": {"title": "post1"}, + "2": {"title": "post2"}, + "3": {"title": "post3"}, +} + + +@app.batch_resolver(type_name="Query", field_name="relatedPosts") +def related_posts(event: AppSyncResolverEvent, post_id: str) -> Optional[Dict]: + return posts_related.get(post_id, None) + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json new file mode 100644 index 00000000000..06bbb2daaae --- /dev/null +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json @@ -0,0 +1,52 @@ +[ + { + "arguments":{ + "post_id":"1" + }, + "identity":"None", + "source":"None", + "request":{ + "headers":{ + "x-forwarded-for":"18.68.31.36", + "sec-ch-ua-mobile":"?0" + } + }, + "prev":"None", + "info":{ + "fieldName":"relatedPosts", + "selectionSetList":[ + "relatedPosts" + ], + "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", + "parentTypeName":"Query", + "variables":{ + + } + } + }, + { + "arguments":{ + "post_id":"10" + }, + "identity":"None", + "source":"None", + "request":{ + "headers":{ + "x-forwarded-for":"18.68.31.36", + "sec-ch-ua-mobile":"?0" + } + }, + "prev":"None", + "info":{ + "fieldName":"relatedPosts", + "selectionSetList":[ + "relatedPosts" + ], + "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", + "parentTypeName":"Query", + "variables":{ + + } + } + } + ] diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index f8f71dc490b..272c82beae4 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -6,6 +6,7 @@ from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.event_handler.appsync import Router +from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayload, ResolverNotFound from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext from tests.functional.utils import load_event @@ -165,6 +166,146 @@ def create_something(id: str): # noqa AA03 VNE003 assert app.current_event.country_viewer == "US" +def test_resolver_include_resolver(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.resolver(type_name="Query", field_name="listLocations") + def get_locations(name: str): + return "get_locations#" + name + + @app.resolver(field_name="listLocations2") + def get_locations2(name: str): + return "get_locations2#" + name + + app.include_router(router) + + # WHEN + mock_event1 = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} + mock_event2 = {"typeName": "Query", "fieldName": "listLocations2", "arguments": {"name": "value"}} + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == "get_locations#value" + assert result2 == "get_locations2#value" + + +def test_resolver_include_mixed_resolver(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.batch_resolver(type_name="Query", field_name="listLocations") + def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + + @app.resolver(field_name="listLocations2") + def get_locations2(name: str) -> str: + return "get_locations2#" + name + + app.include_router(router) + + # WHEN + mock_event1 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Query", + }, + "fieldName": "listLocations", + "arguments": {"name": "value"}, + "source": { + "id": "1", + }, + }, + ] + mock_event2 = { + "typeName": "Query", + "info": { + "fieldName": "listLocations2", + "parentTypeName": "Post", + }, + "fieldName": "listLocations2", + "arguments": {"name": "value"}, + } + + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == ["get_locations#value#1"] + assert result2 == "get_locations2#value" + + +def test_append_context(): + app = AppSyncResolver() + app.append_context(is_admin=True) + assert app._router_context.context.get("is_admin") is True + + +def test_router_append_context(): + router = Router() + router.append_context(is_admin=True) + assert router._router_context.context.get("is_admin") is True + + +def test_route_context_is_cleared_after_resolve(): + # GIVEN + app = AppSyncResolver() + event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} + + @app.resolver(field_name="listLocations") + def get_locations(name: str): + return f"get_locations#{name}" + + # WHEN event resolution kicks in + app.append_context(is_admin=True) + app.resolve(event, {}) + + # THEN context should be empty + assert app._router_context.context == {} + + +def test_router_has_access_to_app_context(): + # GIVEN + app = AppSyncResolver() + router = Router() + event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} + + @router.resolver(type_name="Query", field_name="listLocations") + def get_locations(name: str): + if router._router_context.context.get("is_admin"): + return f"get_locations#{name}" + + app.include_router(router) + + # WHEN + app.append_context(is_admin=True) + ret = app.resolve(event, {}) + + # THEN + assert ret == "get_locations#value" + assert router._router_context.context == {} + + +def test_include_router_merges_context(): + # GIVEN + app = AppSyncResolver() + router = Router() + + # WHEN + app.append_context(is_admin=True) + router.append_context(product_access=True) + + app.include_router(router) + + assert app._router_context.context == router._router_context.context + + +# Batch resolver tests def test_resolve_batch_processing(): event = [ { @@ -219,32 +360,6 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0 assert not app.current_event -def test_resolver_include_resolver(): - # GIVEN - app = AppSyncResolver() - router = Router() - - @router.resolver(type_name="Query", field_name="listLocations") - def get_locations(name: str): - return "get_locations#" + name - - @app.resolver(field_name="listLocations2") - def get_locations2(name: str): - return "get_locations2#" + name - - app.include_router(router) - - # WHEN - mock_event1 = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} - mock_event2 = {"typeName": "Query", "fieldName": "listLocations2", "arguments": {"name": "value"}} - result1 = app.resolve(mock_event1, LambdaContext()) - result2 = app.resolve(mock_event2, LambdaContext()) - - # THEN - assert result1 == "get_locations#value" - assert result2 == "get_locations2#value" - - def test_resolver_batch_resolver_many_fields_with_different_name(): # GIVEN app = AppSyncResolver() @@ -284,7 +399,7 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: }, ] - with pytest.raises(ValueError): + with pytest.raises(InconsistentPayload): app.resolve(mock_event1, LambdaContext()) @@ -317,63 +432,48 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: app.include_router(router) # THEN must fail with ValueError - with pytest.raises(ValueError, match="No resolver found for.*"): + with pytest.raises(ResolverNotFound, match="No resolver found for.*"): app.resolve(mock_event1, LambdaContext()) -def test_resolver_include_batch_resolver(): - # GIVEN +def test_resolver_batch_with_sync_and_async_resolver_at_same_time(): + # GIVEN a AppSyncResolver app = AppSyncResolver() router = Router() - @router.batch_resolver(type_name="Query", field_name="listLocations") - def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] - - @app.batch_resolver(field_name="listLocations2") - def get_locations2(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations2#" + name + "#" + event.source["id"] - - app.include_router(router) - - # WHEN + # WHEN we have an event + # WHEN the event field_name doesn't match with the resolver field_name mock_event1 = [ { "typeName": "Query", "info": { - "fieldName": "listLocations", + "fieldName": "listCars", "parentTypeName": "Query", }, - "fieldName": "listLocations", + "fieldName": "listCars", "arguments": {"name": "value"}, "source": { "id": "1", }, }, ] - mock_event2 = [ - { - "typeName": "Query", - "info": { - "fieldName": "listLocations2", - "parentTypeName": "Post", - }, - "fieldName": "listLocations2", - "arguments": {"name": "value"}, - "source": { - "id": "2", - }, - }, - ] - result1 = app.resolve(mock_event1, LambdaContext()) - result2 = app.resolve(mock_event2, LambdaContext()) - # THEN - assert result1 == ["get_locations#value#1"] - assert result2 == ["get_locations2#value#2"] + @router.batch_resolver(type_name="Query", field_name="listCars") + def get_locations(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] + @router.batch_async_resolver(type_name="Query", field_name="listCars") + async def get_locations_async(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations#" + name + "#" + event.source["id"] -def test_resolver_include_mixed_resolver(): + app.include_router(router) + + # THEN must fail with ValueError + with pytest.warns(UserWarning, match="Both synchronous and asynchronous resolvers*"): + app.resolve(mock_event1, LambdaContext()) + + +def test_resolver_include_batch_resolver(): # GIVEN app = AppSyncResolver() router = Router() @@ -382,9 +482,9 @@ def test_resolver_include_mixed_resolver(): def get_locations(event: AppSyncResolverEvent, name: str) -> str: return "get_locations#" + name + "#" + event.source["id"] - @app.resolver(field_name="listLocations2") - def get_locations2(name: str) -> str: - return "get_locations2#" + name + @app.batch_resolver(field_name="listLocations2") + def get_locations2(event: AppSyncResolverEvent, name: str) -> str: + return "get_locations2#" + name + "#" + event.source["id"] app.include_router(router) @@ -403,22 +503,26 @@ def get_locations2(name: str) -> str: }, }, ] - mock_event2 = { - "typeName": "Query", - "info": { + mock_event2 = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations2", + "parentTypeName": "Post", + }, "fieldName": "listLocations2", - "parentTypeName": "Post", + "arguments": {"name": "value"}, + "source": { + "id": "2", + }, }, - "fieldName": "listLocations2", - "arguments": {"name": "value"}, - } - + ] result1 = app.resolve(mock_event1, LambdaContext()) result2 = app.resolve(mock_event2, LambdaContext()) # THEN assert result1 == ["get_locations#value#1"] - assert result2 == "get_locations2#value" + assert result2 == ["get_locations2#value#2"] def test_resolve_async_batch_processing(): @@ -464,7 +568,7 @@ def test_resolve_async_batch_processing(): app = AppSyncResolver() @app.batch_async_resolver(field_name="listLocations") - async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + async def create_something(event: AppSyncResolverEvent) -> Optional[list]: return event.source["id"] if event.source else None # Call the implicit handler @@ -474,58 +578,6 @@ async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # no assert app.current_batch_event and len(app.current_batch_event) == len(event) -def test_resolve_async_and_sync_batch_processing(): - # GIVEN - app = AppSyncResolver() - router = Router() - - @router.batch_async_resolver(type_name="Query", field_name="listLocations") - async def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] - - @app.batch_resolver(field_name="listLocations2") - def get_locations2(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations2#" + name + "#" + event.source["id"] - - app.include_router(router) - - # WHEN - mock_event1 = [ - { - "typeName": "Query", - "info": { - "fieldName": "listLocations", - "parentTypeName": "Query", - }, - "fieldName": "listLocations", - "arguments": {"name": "value"}, - "source": { - "id": "1", - }, - }, - ] - mock_event2 = [ - { - "typeName": "Query", - "info": { - "fieldName": "listLocations2", - "parentTypeName": "Post", - }, - "fieldName": "listLocations2", - "arguments": {"name": "value"}, - "source": { - "id": "2", - }, - }, - ] - result1 = app.resolve(mock_event1, LambdaContext()) - result2 = app.resolve(mock_event2, LambdaContext()) - - # THEN - assert result1 == ["get_locations#value#1"] - assert result2 == ["get_locations2#value#2"] - - def test_resolve_async_batch_and_sync_singular_processing(): # GIVEN app = AppSyncResolver() @@ -567,7 +619,7 @@ def get_location(name: str) -> str: def test_async_resolver_include_batch_resolver(): - # GIVEN + # GIVEN an AppSyncResolver instance and a Router app = AppSyncResolver() router = Router() @@ -581,7 +633,7 @@ async def get_locations2(event: AppSyncResolverEvent, name: str) -> str: app.include_router(router) - # WHEN + # WHEN two different events needs to be resolved mock_event1 = [ { "typeName": "Query", @@ -610,74 +662,11 @@ async def get_locations2(event: AppSyncResolverEvent, name: str) -> str: }, }, ] + + # WHEN Resolve the events using the AppSyncResolver result1 = app.resolve(mock_event1, LambdaContext()) result2 = app.resolve(mock_event2, LambdaContext()) - # THEN + # THEN Verify that the results match the expected values assert result1 == ["get_locations#value#1"] assert result2 == ["get_locations2#value#2"] - - -def test_append_context(): - app = AppSyncResolver() - app.append_context(is_admin=True) - assert app._router_context.context.get("is_admin") is True - - -def test_router_append_context(): - router = Router() - router.append_context(is_admin=True) - assert router._router_context.context.get("is_admin") is True - - -def test_route_context_is_cleared_after_resolve(): - # GIVEN - app = AppSyncResolver() - event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} - - @app.resolver(field_name="listLocations") - def get_locations(name: str): - return f"get_locations#{name}" - - # WHEN event resolution kicks in - app.append_context(is_admin=True) - app.resolve(event, {}) - - # THEN context should be empty - assert app._router_context.context == {} - - -def test_router_has_access_to_app_context(): - # GIVEN - app = AppSyncResolver() - router = Router() - event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} - - @router.resolver(type_name="Query", field_name="listLocations") - def get_locations(name: str): - if router._router_context.context.get("is_admin"): - return f"get_locations#{name}" - - app.include_router(router) - - # WHEN - app.append_context(is_admin=True) - ret = app.resolve(event, {}) - - # THEN - assert ret == "get_locations#value" - assert router._router_context.context == {} - - -def test_include_router_merges_context(): - # GIVEN - app = AppSyncResolver() - router = Router() - - # WHEN - app.append_context(is_admin=True) - router.append_context(product_access=True) - - app.include_router(router) - - assert app._router_context.context == router._router_context.context From 50894b94e7856c77191241df41379a30a1e65bd6 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 25 Jan 2024 15:55:31 +0000 Subject: [PATCH 18/75] Moving e2e tests to the right folder --- tests/e2e/event_handler/infrastructure.py | 52 +------- tests/e2e/event_handler_appsync/__init__.py | 0 tests/e2e/event_handler_appsync/conftest.py | 19 +++ .../files/schema.graphql | 0 .../handlers/appsync_resolver_handler.py | 0 .../event_handler_appsync/infrastructure.py | 62 ++++++++++ .../test_appsync_resolvers.py | 112 ++++++++++++++++++ 7 files changed, 194 insertions(+), 51 deletions(-) create mode 100644 tests/e2e/event_handler_appsync/__init__.py create mode 100644 tests/e2e/event_handler_appsync/conftest.py rename tests/e2e/{event_handler => event_handler_appsync}/files/schema.graphql (100%) rename tests/e2e/{event_handler => event_handler_appsync}/handlers/appsync_resolver_handler.py (100%) create mode 100644 tests/e2e/event_handler_appsync/infrastructure.py create mode 100644 tests/e2e/event_handler_appsync/test_appsync_resolvers.py diff --git a/tests/e2e/event_handler/infrastructure.py b/tests/e2e/event_handler/infrastructure.py index 188ef0a4bd1..8d7f98045e1 100644 --- a/tests/e2e/event_handler/infrastructure.py +++ b/tests/e2e/event_handler/infrastructure.py @@ -1,12 +1,10 @@ -from pathlib import Path from typing import Dict, Optional -from aws_cdk import CfnOutput, Duration, Expiration +from aws_cdk import CfnOutput from aws_cdk import aws_apigateway as apigwv1 from aws_cdk import aws_apigatewayv2_alpha as apigwv2 from aws_cdk import aws_apigatewayv2_authorizers_alpha as apigwv2authorizers from aws_cdk import aws_apigatewayv2_integrations_alpha as apigwv2integrations -from aws_cdk import aws_appsync_alpha as appsync from aws_cdk import aws_ec2 as ec2 from aws_cdk import aws_elasticloadbalancingv2 as elbv2 from aws_cdk import aws_elasticloadbalancingv2_targets as targets @@ -23,7 +21,6 @@ def create_resources(self): self._create_api_gateway_rest(function=functions["ApiGatewayRestHandler"]) self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"]) self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"]) - self._create_appsync_endpoint(function=functions["AppsyncResolverHandler"]) def _create_alb(self, function: Function): vpc = ec2.Vpc.from_lookup( @@ -94,50 +91,3 @@ def _create_lambda_function_url(self, function: Function): # Maintenance: move auth to IAM when we create sigv4 builders function_url = function.add_function_url(auth_type=FunctionUrlAuthType.AWS_IAM) CfnOutput(self.stack, "LambdaFunctionUrl", value=function_url.url) - - def _create_appsync_endpoint(self, function: Function): - api = appsync.GraphqlApi( - self.stack, - "Api", - name="e2e-tests", - schema=appsync.SchemaFile.from_asset(str(Path(self.feature_path, "files/schema.graphql"))), - authorization_config=appsync.AuthorizationConfig( - default_authorization=appsync.AuthorizationMode( - authorization_type=appsync.AuthorizationType.API_KEY, - api_key_config=appsync.ApiKeyConfig( - description="public key for getting data", - expires=Expiration.after(Duration.hours(25)), - name="API Token", - ), - ), - ), - xray_enabled=False, - ) - lambda_datasource = api.add_lambda_data_source("DataSource", lambda_function=function) - - lambda_datasource.create_resolver( - "QueryGetAllPostsResolver", - type_name="Query", - field_name="allPosts", - ) - lambda_datasource.create_resolver( - "QueryGetPostResolver", - type_name="Query", - field_name="getPost", - ) - lambda_datasource.create_resolver( - "QueryGetPostRelatedResolver", - type_name="Post", - field_name="relatedPosts", - max_batch_size=10, - ) - - lambda_datasource.create_resolver( - "QueryGetPostRelatedAsyncResolver", - type_name="Post", - field_name="relatedPostsAsync", - max_batch_size=10, - ) - - CfnOutput(self.stack, "GraphQLHTTPUrl", value=api.graphql_url) - CfnOutput(self.stack, "GraphQLAPIKey", value=api.api_key) diff --git a/tests/e2e/event_handler_appsync/__init__.py b/tests/e2e/event_handler_appsync/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/e2e/event_handler_appsync/conftest.py b/tests/e2e/event_handler_appsync/conftest.py new file mode 100644 index 00000000000..1f6d8c406de --- /dev/null +++ b/tests/e2e/event_handler_appsync/conftest.py @@ -0,0 +1,19 @@ +import pytest + +from tests.e2e.event_handler_appsync.infrastructure import EventHandlerAppSyncStack + + +@pytest.fixture(autouse=True, scope="package") +def infrastructure(): + """Setup and teardown logic for E2E test infrastructure + + Yields + ------ + Dict[str, str] + CloudFormation Outputs from deployed infrastructure + """ + stack = EventHandlerAppSyncStack() + try: + yield stack.deploy() + finally: + stack.delete() diff --git a/tests/e2e/event_handler/files/schema.graphql b/tests/e2e/event_handler_appsync/files/schema.graphql similarity index 100% rename from tests/e2e/event_handler/files/schema.graphql rename to tests/e2e/event_handler_appsync/files/schema.graphql diff --git a/tests/e2e/event_handler/handlers/appsync_resolver_handler.py b/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py similarity index 100% rename from tests/e2e/event_handler/handlers/appsync_resolver_handler.py rename to tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py diff --git a/tests/e2e/event_handler_appsync/infrastructure.py b/tests/e2e/event_handler_appsync/infrastructure.py new file mode 100644 index 00000000000..155d9f7c2fc --- /dev/null +++ b/tests/e2e/event_handler_appsync/infrastructure.py @@ -0,0 +1,62 @@ +from pathlib import Path + +from aws_cdk import CfnOutput, Duration, Expiration +from aws_cdk import aws_appsync_alpha as appsync +from aws_cdk.aws_lambda import Function + +from tests.e2e.utils.data_builder import build_random_value +from tests.e2e.utils.infrastructure import BaseInfrastructure + + +class EventHandlerAppSyncStack(BaseInfrastructure): + def create_resources(self): + functions = self.create_lambda_functions() + + self._create_appsync_endpoint(function=functions["AppsyncResolverHandler"]) + + def _create_appsync_endpoint(self, function: Function): + api = appsync.GraphqlApi( + self.stack, + "Api", + name=f"e2e-tests{build_random_value()}", + schema=appsync.SchemaFile.from_asset(str(Path(self.feature_path, "files/schema.graphql"))), + authorization_config=appsync.AuthorizationConfig( + default_authorization=appsync.AuthorizationMode( + authorization_type=appsync.AuthorizationType.API_KEY, + api_key_config=appsync.ApiKeyConfig( + description="public key for getting data", + expires=Expiration.after(Duration.hours(25)), + name="API Token", + ), + ), + ), + xray_enabled=False, + ) + lambda_datasource = api.add_lambda_data_source("DataSource", lambda_function=function) + + lambda_datasource.create_resolver( + "QueryGetAllPostsResolver", + type_name="Query", + field_name="allPosts", + ) + lambda_datasource.create_resolver( + "QueryGetPostResolver", + type_name="Query", + field_name="getPost", + ) + lambda_datasource.create_resolver( + "QueryGetPostRelatedResolver", + type_name="Post", + field_name="relatedPosts", + max_batch_size=10, + ) + + lambda_datasource.create_resolver( + "QueryGetPostRelatedAsyncResolver", + type_name="Post", + field_name="relatedPostsAsync", + max_batch_size=10, + ) + + CfnOutput(self.stack, "GraphQLHTTPUrl", value=api.graphql_url) + CfnOutput(self.stack, "GraphQLAPIKey", value=api.api_key) diff --git a/tests/e2e/event_handler_appsync/test_appsync_resolvers.py b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py new file mode 100644 index 00000000000..469c6d4946a --- /dev/null +++ b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py @@ -0,0 +1,112 @@ +import json + +import pytest +from requests import Request + +from tests.e2e.utils import data_fetcher + + +@pytest.fixture +def appsync_endpoint(infrastructure: dict) -> str: + return infrastructure["GraphQLHTTPUrl"] + + +@pytest.fixture +def appsync_access_key(infrastructure: dict) -> str: + return infrastructure["GraphQLAPIKey"] + + +@pytest.mark.xdist_group(name="event_handler") +def test_appsync_get_all_posts(appsync_endpoint, appsync_access_key): + # GIVEN + body = { + "query": "query MyQuery { allPosts { post_id }}", + "variables": None, + "operationName": "MyQuery", + } + + # WHEN + response = data_fetcher.get_http_response( + Request( + method="POST", + url=appsync_endpoint, + json=body, + headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, + ), + ) + + # THEN expect a HTTP 200 response and content return list of Posts + assert response.status_code == 200 + assert response.content is not None + + data = json.loads(response.content.decode("ascii"))["data"] + + assert data["allPosts"] is not None + assert len(data["allPosts"]) > 0 + + +@pytest.mark.xdist_group(name="event_handler") +def test_appsync_get_post(appsync_endpoint, appsync_access_key): + # GIVEN + post_id = "1" + body = { + "query": f'query MyQuery {{ getPost(post_id: "{post_id}") {{ post_id }} }}', + "variables": None, + "operationName": "MyQuery", + } + + # WHEN + response = data_fetcher.get_http_response( + Request( + method="POST", + url=appsync_endpoint, + json=body, + headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, + ), + ) + + # THEN expect a HTTP 200 response and content return Post id + assert response.status_code == 200 + assert response.content is not None + + data = json.loads(response.content.decode("ascii"))["data"] + + assert data["getPost"]["post_id"] == post_id + + +@pytest.mark.xdist_group(name="event_handler") +def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): + # GIVEN + post_id = "2" + related_posts_ids = ["3", "5"] + + body = { + "query": f'query MyQuery {{ getPost(post_id: "{post_id}") \ + {{ post_id relatedPosts {{ post_id }} relatedPostsAsync {{ post_id }} }} }}', + "variables": None, + "operationName": "MyQuery", + } + + # WHEN + response = data_fetcher.get_http_response( + Request( + method="POST", + url=appsync_endpoint, + json=body, + headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, + ), + ) + + # THEN expect a HTTP 200 response and content return Post id with dependent Posts id's + assert response.status_code == 200 + assert response.content is not None + + data = json.loads(response.content.decode("ascii"))["data"] + + assert data["getPost"]["post_id"] == post_id + assert len(data["getPost"]["relatedPosts"]) == len(related_posts_ids) + assert len(data["getPost"]["relatedPostsAsync"]) == len(related_posts_ids) + for post in data["getPost"]["relatedPosts"]: + assert post["post_id"] in related_posts_ids + for post in data["getPost"]["relatedPostsAsync"]: + assert post["post_id"] in related_posts_ids From 3ae6f73b241b944c28381c3b2e1f2a5eccc63165 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 25 Jan 2024 15:56:27 +0000 Subject: [PATCH 19/75] Moving e2e tests to the right folder --- .../event_handler/test_appsync_resolvers.py | 112 ------------------ 1 file changed, 112 deletions(-) delete mode 100644 tests/e2e/event_handler/test_appsync_resolvers.py diff --git a/tests/e2e/event_handler/test_appsync_resolvers.py b/tests/e2e/event_handler/test_appsync_resolvers.py deleted file mode 100644 index 469c6d4946a..00000000000 --- a/tests/e2e/event_handler/test_appsync_resolvers.py +++ /dev/null @@ -1,112 +0,0 @@ -import json - -import pytest -from requests import Request - -from tests.e2e.utils import data_fetcher - - -@pytest.fixture -def appsync_endpoint(infrastructure: dict) -> str: - return infrastructure["GraphQLHTTPUrl"] - - -@pytest.fixture -def appsync_access_key(infrastructure: dict) -> str: - return infrastructure["GraphQLAPIKey"] - - -@pytest.mark.xdist_group(name="event_handler") -def test_appsync_get_all_posts(appsync_endpoint, appsync_access_key): - # GIVEN - body = { - "query": "query MyQuery { allPosts { post_id }}", - "variables": None, - "operationName": "MyQuery", - } - - # WHEN - response = data_fetcher.get_http_response( - Request( - method="POST", - url=appsync_endpoint, - json=body, - headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, - ), - ) - - # THEN expect a HTTP 200 response and content return list of Posts - assert response.status_code == 200 - assert response.content is not None - - data = json.loads(response.content.decode("ascii"))["data"] - - assert data["allPosts"] is not None - assert len(data["allPosts"]) > 0 - - -@pytest.mark.xdist_group(name="event_handler") -def test_appsync_get_post(appsync_endpoint, appsync_access_key): - # GIVEN - post_id = "1" - body = { - "query": f'query MyQuery {{ getPost(post_id: "{post_id}") {{ post_id }} }}', - "variables": None, - "operationName": "MyQuery", - } - - # WHEN - response = data_fetcher.get_http_response( - Request( - method="POST", - url=appsync_endpoint, - json=body, - headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, - ), - ) - - # THEN expect a HTTP 200 response and content return Post id - assert response.status_code == 200 - assert response.content is not None - - data = json.loads(response.content.decode("ascii"))["data"] - - assert data["getPost"]["post_id"] == post_id - - -@pytest.mark.xdist_group(name="event_handler") -def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): - # GIVEN - post_id = "2" - related_posts_ids = ["3", "5"] - - body = { - "query": f'query MyQuery {{ getPost(post_id: "{post_id}") \ - {{ post_id relatedPosts {{ post_id }} relatedPostsAsync {{ post_id }} }} }}', - "variables": None, - "operationName": "MyQuery", - } - - # WHEN - response = data_fetcher.get_http_response( - Request( - method="POST", - url=appsync_endpoint, - json=body, - headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, - ), - ) - - # THEN expect a HTTP 200 response and content return Post id with dependent Posts id's - assert response.status_code == 200 - assert response.content is not None - - data = json.loads(response.content.decode("ascii"))["data"] - - assert data["getPost"]["post_id"] == post_id - assert len(data["getPost"]["relatedPosts"]) == len(related_posts_ids) - assert len(data["getPost"]["relatedPostsAsync"]) == len(related_posts_ids) - for post in data["getPost"]["relatedPosts"]: - assert post["post_id"] in related_posts_ids - for post in data["getPost"]["relatedPostsAsync"]: - assert post["post_id"] in related_posts_ids From 99b03c5791d437862b434a9c199354add366d19c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 26 Jan 2024 17:19:04 +0000 Subject: [PATCH 20/75] Adding partial failure --- .../event_handler/appsync.py | 89 +++++++++++++++++-- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 1c2c53cfde7..8c68274f585 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -337,11 +337,12 @@ def common_field() -> str: return str(uuid.uuid4()) """ - def __init__(self): + def __init__(self, raise_error_on_failed_batch: bool = False): super().__init__() self.current_batch_event: List[AppSyncResolverEvent] = [] self.current_event: Optional[AppSyncResolverEvent] = None self.lambda_context: Optional[LambdaContext] = None + self.raise_error_on_failed_batch = raise_error_on_failed_batch def resolve( self, @@ -448,17 +449,93 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve return resolver(**self.current_event.arguments) def _call_sync_batch_resolver(self, sync_resolver: Callable) -> List[Any]: + """ + Calls a synchronous batch resolver function for each event in the current batch. + + Parameters + ---------- + sync_resolver: Callable + The callable function to resolve events. + + Returns + ------- + List[Any] + A list of results corresponding to the resolved events. + """ + results: List = [] + + # Check if we should raise errors or continue and append None in failed records + if not self.raise_error_on_failed_batch: + for appconfig_event in self.current_batch_event: + try: + results.append(sync_resolver(event=appconfig_event, **appconfig_event.arguments)) + except Exception: + # If an error occurs and raise_error_on_failed_batch is False, + # append None to the results and continue with the next event. + results.append(None) + + return results + return [ sync_resolver(event=appconfig_event, **appconfig_event.arguments) for appconfig_event in self.current_batch_event ] + async def _async_process_batch_event( + self, + async_resolver: Callable, + appconfig_event: AppSyncResolverEvent, + **kwargs, + ): + """ + Asynchronously process a batch event using the provided async_resolver. + + Parameters + ---------- + async_resolver: Callable + The asynchronous resolver function. + appconfig_event: AppSyncResolverEvent + The event to process. + **kwargs + Additional keyword arguments to pass to the resolver. + + Returns + ------- + Any + The result of the resolver function or None if an error occurs and self.raise_error_on_failed_batch is False + """ + + if self.raise_error_on_failed_batch: + try: + return await async_resolver(event=appconfig_event, **kwargs) + except Exception: + return None + + # If raise_error_on_failed_batch is False, proceed without raising errors + return await async_resolver(event=appconfig_event, **kwargs) + async def _call_async_batch_resolver(self, async_resolver: Callable) -> List[Any]: - tasks = [ - asyncio.ensure_future(async_resolver(event=appconfig_event, **appconfig_event.arguments)) - for appconfig_event in self.current_batch_event - ] - return await asyncio.gather(*tasks) + """ + Asynchronously call a batch resolver for each event in the current batch. + + Parameters + ---------- + async_resolver: Callable + The asynchronous resolver function. + + Returns + ------- + List[Any] + A list of results corresponding to the resolved events. + """ + return list( + await asyncio.gather( + *[ + self._async_process_batch_event(async_resolver, appconfig_event, **appconfig_event.arguments) + for appconfig_event in self.current_batch_event + ], + ), + ) def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolverEvent]) -> List[Any]: """Call batch event resolver for sync and async methods From 32be46c9f96f1a0325dfc75868561a87fea5bbe0 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 26 Jan 2024 18:30:29 +0000 Subject: [PATCH 21/75] Adding partial failure --- .../event_handler/appsync.py | 2 +- .../functional/event_handler/test_appsync.py | 210 ++++++++++++++++++ 2 files changed, 211 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 8c68274f585..a98c85c6cba 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -505,7 +505,7 @@ async def _async_process_batch_event( The result of the resolver function or None if an error occurs and self.raise_error_on_failed_batch is False """ - if self.raise_error_on_failed_batch: + if not self.raise_error_on_failed_batch: try: return await async_resolver(event=appconfig_event, **kwargs) except Exception: diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 272c82beae4..2c7bef8bb66 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -360,6 +360,216 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0 assert not app.current_event +def test_resolve_batch_processing_with_raise_on_exception(): + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": [3, 4], + }, + }, + ] + + app = AppSyncResolver(raise_error_on_failed_batch=True) + + @app.batch_resolver(field_name="listLocations") + def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + raise RuntimeError + + # Call the implicit handler + with pytest.raises(RuntimeError): + app.resolve(event, LambdaContext()) + + +def test_async_resolve_batch_processing_with_raise_on_exception(): + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": [3, 4], + }, + }, + ] + + app = AppSyncResolver(raise_error_on_failed_batch=True) + + @app.batch_async_resolver(field_name="listLocations") + def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + raise RuntimeError + + # Call the implicit handler + with pytest.raises(RuntimeError): + app.resolve(event, LambdaContext()) + + +def test_resolve_batch_processing_without_exception(): + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": [3, 4], + }, + }, + ] + + app = AppSyncResolver(raise_error_on_failed_batch=False) + + @app.batch_resolver(field_name="listLocations") + def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + raise RuntimeError + + # Call the implicit handler + result = app.resolve(event, LambdaContext()) + assert result == [None, None, None] + + assert app.current_batch_event and len(app.current_batch_event) == len(event) + assert not app.current_event + + +def test_resolve_async_batch_processing_without_exception(): + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": [3, 4], + }, + }, + ] + + app = AppSyncResolver(raise_error_on_failed_batch=False) + + @app.batch_async_resolver(field_name="listLocations") + def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + raise RuntimeError + + # Call the implicit handler + result = app.resolve(event, LambdaContext()) + assert result == [None, None, None] + + assert app.current_batch_event and len(app.current_batch_event) == len(event) + assert not app.current_event + + def test_resolver_batch_resolver_many_fields_with_different_name(): # GIVEN app = AppSyncResolver() From 4d07d3c4d1dc2ab0ea42a044c57c258555752172 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sat, 27 Jan 2024 16:40:21 +0000 Subject: [PATCH 22/75] Fixing docstring and examples --- .../event_handler/appsync.py | 122 ++++++++++-------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index a98c85c6cba..5e70df6db1d 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -125,21 +125,21 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Ca Examples -------- - ``` - from typing import Optional + ```python + from typing import Optional - from aws_lambda_powertools.event_handler import AppSyncResolver - from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent - from aws_lambda_powertools.utilities.typing import LambdaContext + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext - app = AppSyncResolver() + app = AppSyncResolver() - @app.resolver(type_name="Query", field_name="getPost") - def related_posts(event: AppSyncResolverEvent) -> Optional[list]: - return {"success": "ok"} + @app.resolver(type_name="Query", field_name="getPost") + def related_posts(event: AppSyncResolverEvent) -> Optional[list]: + return {"success": "ok"} - def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) ``` Returns @@ -163,21 +163,21 @@ def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) Examples -------- - ``` - from typing import Optional + ```python + from typing import Optional - from aws_lambda_powertools.event_handler import AppSyncResolver - from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent - from aws_lambda_powertools.utilities.typing import LambdaContext + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext - app = AppSyncResolver() + app = AppSyncResolver() - @app.batch_resolver(type_name="Query", field_name="getPost") - def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: - return {"post_id": id} + @app.batch_resolver(type_name="Query", field_name="getPost") + def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: + return {"post_id": id} - def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) ``` Returns @@ -201,21 +201,21 @@ def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = Examples -------- - ``` - from typing import Optional + ```python + from typing import Optional - from aws_lambda_powertools.event_handler import AppSyncResolver - from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent - from aws_lambda_powertools.utilities.typing import LambdaContext + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext - app = AppSyncResolver() + app = AppSyncResolver() - @app.batch_async_resolver(type_name="Query", field_name="getPost") - async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: - return {"post_id": id} + @app.batch_async_resolver(type_name="Query", field_name="getPost") + async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: + return {"post_id": id} - def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) ``` Returns @@ -308,36 +308,44 @@ def append_context(self, **additional_context) -> None: class AppSyncResolver(Router): """ - AppSync resolver decorator + AppSync GraphQL API Resolver Example ------- - - **Sample usage** - - from aws_lambda_powertools.event_handler import AppSyncResolver - - app = AppSyncResolver() - - @app.resolver(type_name="Query", field_name="listLocations") - def list_locations(page: int = 0, size: int = 10) -> list: - # Your logic to fetch locations with arguments passed in - return [{"id": 100, "name": "Smooth Grooves"}] - - @app.resolver(type_name="Merchant", field_name="extraInfo") - def get_extra_info() -> dict: - # Can use "app.current_event.source" to filter within the parent context - account_type = app.current_event.source["accountType"] - method = "BTC" if account_type == "NEW" else "USD" - return {"preferredPaymentMethod": method} - - @app.resolver(field_name="commonField") - def common_field() -> str: - # Would match all fieldNames matching 'commonField' - return str(uuid.uuid4()) + ```python + from aws_lambda_powertools.event_handler import AppSyncResolver + + app = AppSyncResolver() + + @app.resolver(type_name="Query", field_name="listLocations") + def list_locations(page: int = 0, size: int = 10) -> list: + # Your logic to fetch locations with arguments passed in + return [{"id": 100, "name": "Smooth Grooves"}] + + @app.resolver(type_name="Merchant", field_name="extraInfo") + def get_extra_info() -> dict: + # Can use "app.current_event.source" to filter within the parent context + account_type = app.current_event.source["accountType"] + method = "BTC" if account_type == "NEW" else "USD" + return {"preferredPaymentMethod": method} + + @app.resolver(field_name="commonField") + def common_field() -> str: + # Would match all fieldNames matching 'commonField' + return str(uuid.uuid4()) + ``` """ def __init__(self, raise_error_on_failed_batch: bool = False): + """ + Initialize a new instance of the AppSyncResolver. + + Parameters + ---------- + raise_error_on_failed_batch: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + """ super().__init__() self.current_batch_event: List[AppSyncResolverEvent] = [] self.current_event: Optional[AppSyncResolverEvent] = None From 56442257667c1c7eb581e164b5b423447c60c60c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Sat, 27 Jan 2024 17:26:01 +0000 Subject: [PATCH 23/75] Adding documentation about Handling Exceptions --- docs/core/event_handler/appsync.md | 23 +++++++- .../src/enable_exceptions_batch_resolver.py | 32 ++++++++++++ ...ble_exceptions_batch_resolver_payload.json | 52 +++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py create mode 100644 examples/event_handler_graphql/src/enable_exceptions_batch_resolver_payload.json diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 5c51199978c..9ee813908f7 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -261,10 +261,10 @@ You can use `append_context` when you want to share data between your App and Ro ### Batch processing -We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple events in a batch and prevent multiple lambda executions, configure your Appsync to group events and use the `@batch_resolver` decorator. +We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple events in a batch and prevent multiple lambda executions, configure your Appsync to group events and use the `@batch_resolver` or `@batch_async_resolver` decorators. ???+ info - If you want to understand more how to configure batch processing for the appsynch, please follow this [guide](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"} + If you want to understand more how to configure batch processing for the AppSync, please follow this [guide](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"}. === "getting_started_with_batch_resolver.py" ```python hl_lines="3 7 17" @@ -276,6 +276,25 @@ We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` +#### Handling exceptions + +By default, records that fail during Lambda execution return `None` to ensure the entire batch doesn't fail due to processing errors. However, you have the option to actively return exceptions for debugging or troubleshooting. + +???+ tip + For better error handling, you may need to configure response mapping templates and specify error keys. Explore more on returning individual errors [here](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#advanced-use-case-batching){target="_blank"}. + +=== "enable_exceptions_batch_resolver.py" + ```python hl_lines="3 7 21" + --8<-- "examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py" + ``` + + 1. You can enable the exceptions by setting `raise_error_on_failed_batch` to True. + +=== "enable_exceptions_batch_resolver_payload.json" + ```json hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/enable_exceptions_batch_resolver_payload.json" + ``` + #### Async Choose the asynchronous batch processor when your objective is to leverage concurrency capabilities without the necessity of preserving records in a specific order. diff --git a/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py new file mode 100644 index 00000000000..c1db3bbbc83 --- /dev/null +++ b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py @@ -0,0 +1,32 @@ +from typing import Dict, Optional + +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = AppSyncResolver(raise_error_on_failed_batch=True) # (1)! + + +posts_related = { + "1": {"title": "post1"}, + "2": {"title": "post2"}, + "3": {"title": "post3"}, +} + + +class PostRelatedNotFound(Exception): + ... + + +@app.batch_resolver(type_name="Query", field_name="relatedPosts") +def related_posts(event: AppSyncResolverEvent, post_id: str) -> Optional[Dict]: + post_found = posts_related.get(post_id, None) + + if not post_found: + raise PostRelatedNotFound(f"Unable to find a related post with ID {post_id}.") + + return post_found + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/enable_exceptions_batch_resolver_payload.json b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver_payload.json new file mode 100644 index 00000000000..c0de86728ea --- /dev/null +++ b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver_payload.json @@ -0,0 +1,52 @@ +[ + { + "arguments":{ + "post_id":"12" + }, + "identity":"None", + "source":"None", + "request":{ + "headers":{ + "x-forwarded-for":"18.68.31.36", + "sec-ch-ua-mobile":"?0" + } + }, + "prev":"None", + "info":{ + "fieldName":"relatedPosts", + "selectionSetList":[ + "relatedPosts" + ], + "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", + "parentTypeName":"Query", + "variables":{ + + } + } + }, + { + "arguments":{ + "post_id":"1" + }, + "identity":"None", + "source":"None", + "request":{ + "headers":{ + "x-forwarded-for":"18.68.31.36", + "sec-ch-ua-mobile":"?0" + } + }, + "prev":"None", + "info":{ + "fieldName":"relatedPosts", + "selectionSetList":[ + "relatedPosts" + ], + "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", + "parentTypeName":"Query", + "variables":{ + + } + } + } + ] From b9289963d5ff27e68a4ae26fe3ea08e5bc4c2277 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 29 Jan 2024 18:58:18 +0000 Subject: [PATCH 24/75] Fixing docstring --- aws_lambda_powertools/event_handler/appsync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 5e70df6db1d..f35b98840db 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -190,7 +190,7 @@ def lambda_handler(event, context: LambdaContext) -> dict: @abstractmethod def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: """ - Retrieve a batch resolver function for a specific type and field. + Retrieve a batch resolver function for a specific type and field and runs async. Parameters ----------- @@ -358,7 +358,7 @@ def resolve( context: LambdaContext, data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, ) -> Any: - """Resolve field_name in single event or in a batch event + """Resolves the response based on the provide event and decorator routes Parameters ---------- From f81a00395885138758cc11037cf3516b524b428e Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 7 Feb 2024 00:57:30 +0000 Subject: [PATCH 25/75] Adding fine grained control when handling exceptions --- .../event_handler/appsync.py | 388 ++++++------------ .../event_handler/graphql_appsync/base.py | 234 +++++++++++ docs/core/event_handler/appsync.md | 2 +- .../functional/event_handler/test_appsync.py | 16 +- 4 files changed, 362 insertions(+), 278 deletions(-) create mode 100644 aws_lambda_powertools/event_handler/graphql_appsync/base.py diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index f35b98840db..4d33ff16b4d 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,10 +1,10 @@ import asyncio import logging import warnings -from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List, Optional, Type, Union from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayload, ResolverNotFound +from aws_lambda_powertools.event_handler.graphql_appsync.base import BasePublic, BaseResolverRegistry from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -30,214 +30,6 @@ def context(self): self._context.clear() -class BaseResolverRegistry(ABC): - """ - Abstract base class for a resolver registry. - - This class defines the interface for managing and retrieving resolvers - for various type and field combinations. - """ - - @property - @abstractmethod - def resolvers(self) -> Dict[str, Dict[str, Any]]: - """ - Get the dictionary of resolvers. - - Returns - ------- - dict - A dictionary containing resolver information. - """ - raise NotImplementedError - - @resolvers.setter - @abstractmethod - def resolvers(self, resolvers: dict) -> None: - """ - Set the dictionary of resolvers. - - Parameters - ---------- - resolvers: dict - A dictionary containing resolver information. - """ - raise NotImplementedError - - @abstractmethod - def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - """ - Retrieve a resolver function for a specific type and field. - - Parameters - ----------- - type_name: str - The name of the type. - field_name: Optional[str] - The name of the field (default is None). - - Returns - ------- - Callable - The resolver function. - """ - raise NotImplementedError - - @abstractmethod - def find_resolver(self, type_name: str, field_name: str) -> Optional[Callable]: - """ - Find a resolver function for a specific type and field. - - Parameters - ----------- - type_name: str - The name of the type. - field_name: str - The name of the field. - - Returns - ------- - Optional[Callable] - The resolver function. None if not found. - """ - raise NotImplementedError - - -class BasePublic(ABC): - """ - Abstract base class for public interface methods for resolver. - - This class outlines the methods that must be implemented by subclasses to manage resolvers and - context information. - """ - - @abstractmethod - def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - """ - Retrieve a resolver function for a specific type and field. - - Parameters - ----------- - type_name: str - The name of the type. - field_name: Optional[str] - The name of the field (default is None). - - Examples - -------- - ```python - from typing import Optional - - from aws_lambda_powertools.event_handler import AppSyncResolver - from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent - from aws_lambda_powertools.utilities.typing import LambdaContext - - app = AppSyncResolver() - - @app.resolver(type_name="Query", field_name="getPost") - def related_posts(event: AppSyncResolverEvent) -> Optional[list]: - return {"success": "ok"} - - def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) - ``` - - Returns - ------- - Callable - The resolver function. - """ - raise NotImplementedError - - @abstractmethod - def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - """ - Retrieve a batch resolver function for a specific type and field. - - Parameters - ----------- - type_name: str - The name of the type. - field_name: Optional[str] - The name of the field (default is None). - - Examples - -------- - ```python - from typing import Optional - - from aws_lambda_powertools.event_handler import AppSyncResolver - from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent - from aws_lambda_powertools.utilities.typing import LambdaContext - - app = AppSyncResolver() - - @app.batch_resolver(type_name="Query", field_name="getPost") - def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: - return {"post_id": id} - - def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) - ``` - - Returns - ------- - Callable - The batch resolver function. - """ - raise NotImplementedError - - @abstractmethod - def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - """ - Retrieve a batch resolver function for a specific type and field and runs async. - - Parameters - ----------- - type_name: str - The name of the type. - field_name: Optional[str] - The name of the field (default is None). - - Examples - -------- - ```python - from typing import Optional - - from aws_lambda_powertools.event_handler import AppSyncResolver - from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent - from aws_lambda_powertools.utilities.typing import LambdaContext - - app = AppSyncResolver() - - @app.batch_async_resolver(type_name="Query", field_name="getPost") - async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: - return {"post_id": id} - - def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) - ``` - - Returns - ------- - Callable - The batch resolver function. - """ - raise NotImplementedError - - @abstractmethod - def append_context(self, **additional_context) -> None: - """ - Append additional context information. - - Parameters - ----------- - **additional_context: dict - Additional context key-value pairs to append. - """ - raise NotImplementedError - - class ResolverRegistry(BaseResolverRegistry): def __init__(self): self._resolvers: Dict[str, Dict[str, Any]] = {} @@ -250,7 +42,7 @@ def resolvers(self) -> Dict[str, Dict[str, Any]]: def resolvers(self, resolvers: dict) -> None: self._resolvers.update(resolvers) - def resolver(self, type_name: str = "*", field_name: Optional[str] = None): + def resolver(self, type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False): """Registers the resolver for field_name Parameters @@ -259,16 +51,24 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None): Type name field_name : str Field name + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + + Return + ---------- + Dict + A dictionary with the resolver and if raise exception on error """ def register(func): logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") - self._resolvers[f"{type_name}.{field_name}"] = {"func": func} + self._resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} return func return register - def find_resolver(self, type_name: str, field_name: str) -> Optional[Callable]: + def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: """Find resolver based on type_name and field_name Parameters @@ -277,12 +77,16 @@ def find_resolver(self, type_name: str, field_name: str) -> Optional[Callable]: Type name field_name : str Field name + Return + ---------- + Optional[Dict] + A dictionary with the resolver and if raise exception on error """ resolver = self._resolvers.get(f"{type_name}.{field_name}", self._resolvers.get(f"*.{field_name}")) if not resolver: return None - return resolver["func"] + return resolver class Router(BasePublic): @@ -296,11 +100,29 @@ def __init__(self): def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) - def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def batch_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + return self._batch_resolver_registry.resolver( + field_name=field_name, + type_name=type_name, + raise_on_error=raise_on_error, + ) - def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._batch_async_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def batch_async_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + return self._batch_async_resolver_registry.resolver( + field_name=field_name, + type_name=type_name, + raise_on_error=raise_on_error, + ) def append_context(self, **additional_context) -> None: self._router_context.context = additional_context @@ -336,21 +158,23 @@ def common_field() -> str: ``` """ - def __init__(self, raise_error_on_failed_batch: bool = False): + def __init__(self): """ Initialize a new instance of the AppSyncResolver. - - Parameters - ---------- - raise_error_on_failed_batch: bool - A flag indicating whether to raise an error when processing batches - with failed items. Defaults to False, which means errors are handled without raising exceptions. """ super().__init__() self.current_batch_event: List[AppSyncResolverEvent] = [] self.current_event: Optional[AppSyncResolverEvent] = None self.lambda_context: Optional[LambdaContext] = None - self.raise_error_on_failed_batch = raise_error_on_failed_batch + + def __call__( + self, + event: dict, + context: LambdaContext, + data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, + ) -> Any: + """Implicit lambda handler which internally calls `resolve`""" + return self.resolve(event, context, data_model) def resolve( self, @@ -454,9 +278,9 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve resolver = self._resolver_registry.find_resolver(self.current_event.type_name, self.current_event.field_name) if not resolver: raise ValueError(f"No resolver found for '{self.current_event.type_name}.{self.current_event.field_name}'") - return resolver(**self.current_event.arguments) + return resolver["func"](**self.current_event.arguments) - def _call_sync_batch_resolver(self, sync_resolver: Callable) -> List[Any]: + def _call_sync_batch_resolver(self, sync_resolver: Callable, raise_on_error: bool = False) -> List[Any]: """ Calls a synchronous batch resolver function for each event in the current batch. @@ -464,16 +288,19 @@ def _call_sync_batch_resolver(self, sync_resolver: Callable) -> List[Any]: ---------- sync_resolver: Callable The callable function to resolve events. + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. Returns ------- List[Any] A list of results corresponding to the resolved events. """ - results: List = [] # Check if we should raise errors or continue and append None in failed records - if not self.raise_error_on_failed_batch: + if not raise_on_error: + results: List = [] for appconfig_event in self.current_batch_event: try: results.append(sync_resolver(event=appconfig_event, **appconfig_event.arguments)) @@ -489,10 +316,42 @@ def _call_sync_batch_resolver(self, sync_resolver: Callable) -> List[Any]: for appconfig_event in self.current_batch_event ] + async def _call_async_batch_resolver(self, async_resolver: Callable, raise_on_error: bool = False) -> List[Any]: + """ + Asynchronously call a batch resolver for each event in the current batch. + + Parameters + ---------- + async_resolver: Callable + The asynchronous resolver function. + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + + Returns + ------- + List[Any] + A list of results corresponding to the resolved events. + """ + return list( + await asyncio.gather( + *[ + self._async_process_batch_event( + async_resolver, + appconfig_event, + **appconfig_event.arguments, + raise_on_error=raise_on_error, + ) + for appconfig_event in self.current_batch_event + ], + ), + ) + async def _async_process_batch_event( self, async_resolver: Callable, appconfig_event: AppSyncResolverEvent, + raise_on_error: bool = False, **kwargs, ): """ @@ -504,6 +363,9 @@ async def _async_process_batch_event( The asynchronous resolver function. appconfig_event: AppSyncResolverEvent The event to process. + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. **kwargs Additional keyword arguments to pass to the resolver. @@ -513,7 +375,7 @@ async def _async_process_batch_event( The result of the resolver function or None if an error occurs and self.raise_error_on_failed_batch is False """ - if not self.raise_error_on_failed_batch: + if not raise_on_error: try: return await async_resolver(event=appconfig_event, **kwargs) except Exception: @@ -522,29 +384,6 @@ async def _async_process_batch_event( # If raise_error_on_failed_batch is False, proceed without raising errors return await async_resolver(event=appconfig_event, **kwargs) - async def _call_async_batch_resolver(self, async_resolver: Callable) -> List[Any]: - """ - Asynchronously call a batch resolver for each event in the current batch. - - Parameters - ---------- - async_resolver: Callable - The asynchronous resolver function. - - Returns - ------- - List[Any] - A list of results corresponding to the resolved events. - """ - return list( - await asyncio.gather( - *[ - self._async_process_batch_event(async_resolver, appconfig_event, **appconfig_event.arguments) - for appconfig_event in self.current_batch_event - ], - ), - ) - def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolverEvent]) -> List[Any]: """Call batch event resolver for sync and async methods @@ -583,26 +422,19 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv if resolver and async_resolver: warnings.warn( f"Both synchronous and asynchronous resolvers found for the same event and field." - f"The synchronous resolver takes precedence. Executing: {resolver.__name__}", + f"The synchronous resolver takes precedence. Executing: {resolver['func'].__name__}", stacklevel=2, ) if resolver: - return self._call_sync_batch_resolver(resolver) + return self._call_sync_batch_resolver(resolver["func"], resolver["raise_on_error"]) elif async_resolver: - return asyncio.run(self._call_async_batch_resolver(async_resolver)) + return asyncio.run( + self._call_async_batch_resolver(async_resolver["func"], async_resolver["raise_on_error"]), + ) else: raise ResolverNotFound(f"No resolver found for '{type_name}.{field_name}'") - def __call__( - self, - event: dict, - context: LambdaContext, - data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, - ) -> Any: - """Implicit lambda handler which internally calls `resolve`""" - return self.resolve(event, context, data_model) - def include_router(self, router: "Router") -> None: """Adds all resolvers defined in a router @@ -625,11 +457,29 @@ def include_router(self, router: "Router") -> None: def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) - def batch_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._batch_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def batch_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + return self._batch_resolver_registry.resolver( + field_name=field_name, + type_name=type_name, + raise_on_error=raise_on_error, + ) - def batch_async_resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._batch_async_resolver_registry.resolver(field_name=field_name, type_name=type_name) + def batch_async_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + return self._batch_async_resolver_registry.resolver( + field_name=field_name, + type_name=type_name, + raise_on_error=raise_on_error, + ) def append_context(self, **additional_context) -> None: self._router_context.context = additional_context diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/base.py b/aws_lambda_powertools/event_handler/graphql_appsync/base.py new file mode 100644 index 00000000000..a64c509b19a --- /dev/null +++ b/aws_lambda_powertools/event_handler/graphql_appsync/base.py @@ -0,0 +1,234 @@ +from abc import ABC, abstractmethod +from typing import Any, Callable, Dict, Optional + + +class BaseResolverRegistry(ABC): + """ + Abstract base class for a resolver registry. + + This class defines the interface for managing and retrieving resolvers + for various type and field combinations. + """ + + @property + @abstractmethod + def resolvers(self) -> Dict[str, Dict[str, Any]]: + """ + Get the dictionary of resolvers. + + Returns + ------- + dict + A dictionary containing resolver information. + """ + raise NotImplementedError + + @resolvers.setter + @abstractmethod + def resolvers(self, resolvers: dict) -> None: + """ + Set the dictionary of resolvers. + + Parameters + ---------- + resolvers: dict + A dictionary containing resolver information. + """ + raise NotImplementedError + + @abstractmethod + def resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ): + """ + Retrieve a resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + + Returns + ------- + Callable + The resolver function. + """ + raise NotImplementedError + + @abstractmethod + def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: + """ + Find a resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: str + The name of the field. + + Returns + ------- + Optional[Callable] + The resolver function. None if not found. + """ + raise NotImplementedError + + +class BasePublic(ABC): + """ + Abstract base class for public interface methods for resolver. + + This class outlines the methods that must be implemented by subclasses to manage resolvers and + context information. + """ + + @abstractmethod + def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + """ + Retrieve a resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + + Examples + -------- + ```python + from typing import Optional + + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext + + app = AppSyncResolver() + + @app.resolver(type_name="Query", field_name="getPost") + def related_posts(event: AppSyncResolverEvent) -> Optional[list]: + return {"success": "ok"} + + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) + ``` + + Returns + ------- + Callable + The resolver function. + """ + raise NotImplementedError + + @abstractmethod + def batch_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + """ + Retrieve a batch resolver function for a specific type and field. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + + Examples + -------- + ```python + from typing import Optional + + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext + + app = AppSyncResolver() + + @app.batch_resolver(type_name="Query", field_name="getPost") + def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: + return {"post_id": id} + + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) + ``` + + Returns + ------- + Callable + The batch resolver function. + """ + raise NotImplementedError + + @abstractmethod + def batch_async_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + """ + Retrieve a batch resolver function for a specific type and field and runs async. + + Parameters + ----------- + type_name: str + The name of the type. + field_name: Optional[str] + The name of the field (default is None). + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + + Examples + -------- + ```python + from typing import Optional + + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent + from aws_lambda_powertools.utilities.typing import LambdaContext + + app = AppSyncResolver() + + @app.batch_async_resolver(type_name="Query", field_name="getPost") + async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: + return {"post_id": id} + + def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) + ``` + + Returns + ------- + Callable + The batch resolver function. + """ + raise NotImplementedError + + @abstractmethod + def append_context(self, **additional_context) -> None: + """ + Append additional context information. + + Parameters + ----------- + **additional_context: dict + Additional context key-value pairs to append. + """ + raise NotImplementedError diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 7ca9ac74b7e..b01ab8118ca 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -288,7 +288,7 @@ By default, records that fail during Lambda execution return `None` to ensure th --8<-- "examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py" ``` - 1. You can enable the exceptions by setting `raise_error_on_failed_batch` to True. + 1. You can enable the exceptions by setting `raise_on_error` to True. === "enable_exceptions_batch_resolver_payload.json" ```json hl_lines="4 16 21 29 41 46" diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 2ee1b447c25..dc4963ca53f 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -398,9 +398,9 @@ def test_resolve_batch_processing_with_raise_on_exception(): }, ] - app = AppSyncResolver(raise_error_on_failed_batch=True) + app = AppSyncResolver() - @app.batch_resolver(field_name="listLocations") + @app.batch_resolver(field_name="listLocations", raise_on_error=True) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -449,9 +449,9 @@ def test_async_resolve_batch_processing_with_raise_on_exception(): }, ] - app = AppSyncResolver(raise_error_on_failed_batch=True) + app = AppSyncResolver() - @app.batch_async_resolver(field_name="listLocations") + @app.batch_async_resolver(field_name="listLocations", raise_on_error=True) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -500,9 +500,9 @@ def test_resolve_batch_processing_without_exception(): }, ] - app = AppSyncResolver(raise_error_on_failed_batch=False) + app = AppSyncResolver() - @app.batch_resolver(field_name="listLocations") + @app.batch_resolver(field_name="listLocations", raise_on_error=False) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -554,9 +554,9 @@ def test_resolve_async_batch_processing_without_exception(): }, ] - app = AppSyncResolver(raise_error_on_failed_batch=False) + app = AppSyncResolver() - @app.batch_async_resolver(field_name="listLocations") + @app.batch_async_resolver(field_name="listLocations", raise_on_error=False) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError From 25fff55ed9d1df79692ab3f17a5e07f5f4517899 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 7 Feb 2024 01:01:03 +0000 Subject: [PATCH 26/75] Adding fine grained control when handling exceptions --- .../src/enable_exceptions_batch_resolver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py index c1db3bbbc83..f77374527ea 100644 --- a/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py +++ b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py @@ -4,7 +4,7 @@ from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext -app = AppSyncResolver(raise_error_on_failed_batch=True) # (1)! +app = AppSyncResolver() posts_related = { @@ -18,7 +18,7 @@ class PostRelatedNotFound(Exception): ... -@app.batch_resolver(type_name="Query", field_name="relatedPosts") +@app.batch_resolver(type_name="Query", field_name="relatedPosts", raise_on_error=True) # (1)! def related_posts(event: AppSyncResolverEvent, post_id: str) -> Optional[Dict]: post_found = posts_related.get(post_id, None) From cbe7190f00b766846f8f16756981904055f774a8 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Feb 2024 11:05:31 +0100 Subject: [PATCH 27/75] docs: add intro diagram --- docs/core/event_handler/appsync.md | 32 +++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index b01ab8118ca..a6828937aec 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -3,7 +3,37 @@ title: GraphQL API description: Core utility --- -Event handler for AWS AppSync Direct Lambda Resolver and Amplify GraphQL Transformer. +Event Handler for AWS AppSync and Amplify GraphQL Transformer. + +```mermaid +stateDiagram-v2 + direction LR + EventSource: AWS Lambda Event Sources + EventHandlerResolvers: AWS AppSync Direct invocation

AWS AppSync Batch invocation + LambdaInit: Lambda invocation + EventHandler: Event Handler + EventHandlerResolver: Route event based on GraphQL type/field keys + YourLogic: Run your registered resolver function + EventHandlerResolverBuilder: Adapts response to Event Source contract + LambdaResponse: Lambda response + + state EventSource { + EventHandlerResolvers + } + + EventHandlerResolvers --> LambdaInit + + LambdaInit --> EventHandler + EventHandler --> EventHandlerResolver + + state EventHandler { + [*] --> EventHandlerResolver: app.resolve(event, context) + EventHandlerResolver --> YourLogic + YourLogic --> EventHandlerResolverBuilder + } + + EventHandler --> LambdaResponse +``` ## Key Features From f10a02cba86578ad339fe0ed060c181f66d597ff Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Feb 2024 11:17:15 +0100 Subject: [PATCH 28/75] docs: fix wording (Tech debt) --- docs/core/event_handler/appsync.md | 19 +++++++++---------- .../event_handler_graphql/sam/template.yaml | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index a6828937aec..ad5258e7ee6 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -37,36 +37,35 @@ stateDiagram-v2 ## Key Features -* Automatically parse API arguments to function arguments * Choose between strictly match a GraphQL field name or all of them to a function -* Integrates with [Data classes utilities](../../utilities/data_classes.md){target="_blank"} to access resolver and identity information -* Works with both Direct Lambda Resolver and Amplify GraphQL Transformer `@function` directive -* Support async Python 3.8+ functions, and generators +* Automatically parse API arguments to function arguments +* Integrates with [Event Source Data classes utilities](../../utilities/data_classes.md){target="_blank"} to access resolver and identity information +* Support async Python 3.8+ functions and generators ## Terminology **[Direct Lambda Resolver](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html){target="_blank"}**. A custom AppSync Resolver to bypass the use of Apache Velocity Template (VTL) and automatically map your function's response to a GraphQL field. -**[Amplify GraphQL Transformer](https://docs.amplify.aws/cli/graphql-transformer/function){target="_blank"}**. Custom GraphQL directives to define your application's data model using Schema Definition Language (SDL). Amplify CLI uses these directives to convert GraphQL SDL into full descriptive AWS CloudFormation templates. +**[Amplify GraphQL Transformer](https://docs.amplify.aws/cli/graphql-transformer/function){target="_blank"}**. Custom GraphQL directives to define your application's data model using Schema Definition Language _(SDL)_, _e.g., `@function`_. Amplify CLI uses these directives to convert GraphQL SDL into full descriptive AWS CloudFormation templates. ## Getting started +???+ tip "Tip: Designing GraphQL Schemas for the first time?" + Visit [AWS AppSync schema documentation](https://docs.aws.amazon.com/appsync/latest/devguide/designing-your-schema.html){target="_blank"} to understand how to define types, nesting, and pagination. + ### Required resources -You must have an existing AppSync GraphQL API and IAM permissions to invoke your Lambda function. That said, there is no additional permissions to use this utility. +You must have an existing AppSync GraphQL API and IAM permissions to invoke your Lambda function. That said, there is no additional permissions to use Event Handler as routing requires no dependency (_standard library_). This is the sample infrastructure we are using for the initial examples with a AppSync Direct Lambda Resolver. -???+ tip "Tip: Designing GraphQL Schemas for the first time?" - Visit [AWS AppSync schema documentation](https://docs.aws.amazon.com/appsync/latest/devguide/designing-your-schema.html){target="_blank"} for understanding how to define types, nesting, and pagination. - === "getting_started_schema.graphql" ```typescript --8<-- "examples/event_handler_graphql/src/getting_started_schema.graphql" ``` -=== "template.yml" +=== "template.yaml" ```yaml hl_lines="59-60 71-72 94-95 104-105 112-113" --8<-- "examples/event_handler_graphql/sam/template.yaml" diff --git a/examples/event_handler_graphql/sam/template.yaml b/examples/event_handler_graphql/sam/template.yaml index bc4faa34319..1c75d18ae55 100644 --- a/examples/event_handler_graphql/sam/template.yaml +++ b/examples/event_handler_graphql/sam/template.yaml @@ -5,7 +5,7 @@ Description: Hello world Direct Lambda Resolver Globals: Function: Timeout: 5 - Runtime: python3.9 + Runtime: python3.12 Tracing: Active Environment: Variables: From f0c7b3685a4929ba1d5c95f80602e625443b6bbb Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 21 Feb 2024 16:27:33 +0100 Subject: [PATCH 29/75] refactor: use async_ prefix for async code --- aws_lambda_powertools/event_handler/appsync.py | 4 ++-- .../event_handler/graphql_appsync/base.py | 4 ++-- docs/core/event_handler/appsync.md | 2 +- .../getting_started_with_batch_async_resolver.py | 2 +- .../handlers/appsync_resolver_handler.py | 2 +- tests/functional/event_handler/test_appsync.py | 14 +++++++------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 4d33ff16b4d..07d3e374cf4 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -112,7 +112,7 @@ def batch_resolver( raise_on_error=raise_on_error, ) - def batch_async_resolver( + def async_batch_resolver( self, type_name: str = "*", field_name: Optional[str] = None, @@ -469,7 +469,7 @@ def batch_resolver( raise_on_error=raise_on_error, ) - def batch_async_resolver( + def async_batch_resolver( self, type_name: str = "*", field_name: Optional[str] = None, diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/base.py b/aws_lambda_powertools/event_handler/graphql_appsync/base.py index a64c509b19a..f3df4fabf16 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/base.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/base.py @@ -176,7 +176,7 @@ def lambda_handler(event, context: LambdaContext) -> dict: raise NotImplementedError @abstractmethod - def batch_async_resolver( + def async_batch_resolver( self, type_name: str = "*", field_name: Optional[str] = None, @@ -206,7 +206,7 @@ def batch_async_resolver( app = AppSyncResolver() - @app.batch_async_resolver(type_name="Query", field_name="getPost") + @app.async_batch_resolver(type_name="Query", field_name="getPost") async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]: return {"post_id": id} diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index ad5258e7ee6..ceb71f0a527 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -290,7 +290,7 @@ You can use `append_context` when you want to share data between your App and Ro ### Batch processing -We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple events in a batch and prevent multiple lambda executions, configure your Appsync to group events and use the `@batch_resolver` or `@batch_async_resolver` decorators. +We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple events in a batch and prevent multiple lambda executions, configure your Appsync to group events and use the `@batch_resolver` or `@async_batch_resolver` decorators. ???+ info If you want to understand more how to configure batch processing for the AppSync, please follow this [guide](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"}. diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py index b155afe290e..c161073b5c8 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py @@ -14,7 +14,7 @@ } -@app.batch_async_resolver(type_name="Query", field_name="relatedPosts") +@app.async_batch_resolver(type_name="Query", field_name="relatedPosts") async def related_posts(event: AppSyncResolverEvent, post_id: str) -> Optional[Dict]: return posts_related.get(post_id, None) diff --git a/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py b/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py index 60febd49640..e08b0b93a29 100644 --- a/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py +++ b/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py @@ -92,7 +92,7 @@ def related_posts(event: AppSyncResolverEvent) -> Optional[list]: return posts_related[event.source["post_id"]] if event.source else None -@app.batch_async_resolver(type_name="Post", field_name="relatedPostsAsync") +@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsync") async def related_posts_async(event: AppSyncResolverEvent) -> Optional[list]: return posts_related[event.source["post_id"]] if event.source else None diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index dc4963ca53f..18ec671901e 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -451,7 +451,7 @@ def test_async_resolve_batch_processing_with_raise_on_exception(): app = AppSyncResolver() - @app.batch_async_resolver(field_name="listLocations", raise_on_error=True) + @app.async_batch_resolver(field_name="listLocations", raise_on_error=True) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -556,7 +556,7 @@ def test_resolve_async_batch_processing_without_exception(): app = AppSyncResolver() - @app.batch_async_resolver(field_name="listLocations", raise_on_error=False) + @app.async_batch_resolver(field_name="listLocations", raise_on_error=False) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -670,7 +670,7 @@ def test_resolver_batch_with_sync_and_async_resolver_at_same_time(): def get_locations(event: AppSyncResolverEvent, name: str) -> str: return "get_locations#" + name + "#" + event.source["id"] - @router.batch_async_resolver(type_name="Query", field_name="listCars") + @router.async_batch_resolver(type_name="Query", field_name="listCars") async def get_locations_async(event: AppSyncResolverEvent, name: str) -> str: return "get_locations#" + name + "#" + event.source["id"] @@ -775,7 +775,7 @@ def test_resolve_async_batch_processing(): app = AppSyncResolver() - @app.batch_async_resolver(field_name="listLocations") + @app.async_batch_resolver(field_name="listLocations") async def create_something(event: AppSyncResolverEvent) -> Optional[list]: return event.source["id"] if event.source else None @@ -791,7 +791,7 @@ def test_resolve_async_batch_and_sync_singular_processing(): app = AppSyncResolver() router = Router() - @router.batch_async_resolver(type_name="Query", field_name="listLocations") + @router.async_batch_resolver(type_name="Query", field_name="listLocations") async def get_locations(event: AppSyncResolverEvent, name: str) -> str: return "get_locations#" + name + "#" + event.source["id"] @@ -831,11 +831,11 @@ def test_async_resolver_include_batch_resolver(): app = AppSyncResolver() router = Router() - @router.batch_async_resolver(type_name="Query", field_name="listLocations") + @router.async_batch_resolver(type_name="Query", field_name="listLocations") async def get_locations(event: AppSyncResolverEvent, name: str) -> str: return "get_locations#" + name + "#" + event.source["id"] - @app.batch_async_resolver(field_name="listLocations2") + @app.async_batch_resolver(field_name="listLocations2") async def get_locations2(event: AppSyncResolverEvent, name: str) -> str: return "get_locations2#" + name + "#" + event.source["id"] From db6d67db2c9cc5e3b9f055828db546d5a64a5b2d Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 21 Feb 2024 17:02:08 +0100 Subject: [PATCH 30/75] refactor: move router to a separate file to ease maintenance --- .../event_handler/appsync.py | 122 +---------------- .../event_handler/graphql_appsync/__init__.py | 0 .../event_handler/graphql_appsync/router.py | 123 ++++++++++++++++++ .../split_operation_append_context_module.py | 2 +- .../src/split_operation_module.py | 2 +- .../functional/event_handler/test_appsync.py | 4 +- 6 files changed, 128 insertions(+), 125 deletions(-) create mode 100644 aws_lambda_powertools/event_handler/graphql_appsync/__init__.py create mode 100644 aws_lambda_powertools/event_handler/graphql_appsync/router.py diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 07d3e374cf4..262fd38b95c 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,132 +1,12 @@ import asyncio -import logging import warnings from typing import Any, Callable, Dict, List, Optional, Type, Union from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayload, ResolverNotFound -from aws_lambda_powertools.event_handler.graphql_appsync.base import BasePublic, BaseResolverRegistry +from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext -logger = logging.getLogger(__name__) - - -class RouterContext: - def __init__(self): - self._context = {} - - @property - def context(self) -> Dict[str, Any]: - return self._context - - @context.setter - def context(self, additional_context: Dict[str, Any]) -> None: - """Append key=value data as routing context""" - self._context.update(**additional_context) - - @context.deleter - def context(self): - """Resets routing context""" - self._context.clear() - - -class ResolverRegistry(BaseResolverRegistry): - def __init__(self): - self._resolvers: Dict[str, Dict[str, Any]] = {} - - @property - def resolvers(self) -> Dict[str, Dict[str, Any]]: - return self._resolvers - - @resolvers.setter - def resolvers(self, resolvers: dict) -> None: - self._resolvers.update(resolvers) - - def resolver(self, type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False): - """Registers the resolver for field_name - - Parameters - ---------- - type_name : str - Type name - field_name : str - Field name - raise_on_error: bool - A flag indicating whether to raise an error when processing batches - with failed items. Defaults to False, which means errors are handled without raising exceptions. - - Return - ---------- - Dict - A dictionary with the resolver and if raise exception on error - """ - - def register(func): - logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") - self._resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} - return func - - return register - - def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: - """Find resolver based on type_name and field_name - - Parameters - ---------- - type_name : str - Type name - field_name : str - Field name - Return - ---------- - Optional[Dict] - A dictionary with the resolver and if raise exception on error - """ - - resolver = self._resolvers.get(f"{type_name}.{field_name}", self._resolvers.get(f"*.{field_name}")) - if not resolver: - return None - return resolver - - -class Router(BasePublic): - def __init__(self): - self._resolver_registry: BaseResolverRegistry = ResolverRegistry() - self._batch_resolver_registry: BaseResolverRegistry = ResolverRegistry() - self._batch_async_resolver_registry: BaseResolverRegistry = ResolverRegistry() - self._router_context: RouterContext = RouterContext() - - # Interfaces - def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) - - def batch_resolver( - self, - type_name: str = "*", - field_name: Optional[str] = None, - raise_on_error: bool = False, - ) -> Callable: - return self._batch_resolver_registry.resolver( - field_name=field_name, - type_name=type_name, - raise_on_error=raise_on_error, - ) - - def async_batch_resolver( - self, - type_name: str = "*", - field_name: Optional[str] = None, - raise_on_error: bool = False, - ) -> Callable: - return self._batch_async_resolver_registry.resolver( - field_name=field_name, - type_name=type_name, - raise_on_error=raise_on_error, - ) - - def append_context(self, **additional_context) -> None: - self._router_context.context = additional_context - class AppSyncResolver(Router): """ diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/__init__.py b/aws_lambda_powertools/event_handler/graphql_appsync/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py new file mode 100644 index 00000000000..983d577c4a2 --- /dev/null +++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py @@ -0,0 +1,123 @@ +import logging +from typing import Any, Callable, Dict, Optional + +from aws_lambda_powertools.event_handler.graphql_appsync.base import BasePublic, BaseResolverRegistry + +logger = logging.getLogger(__name__) + + +class RouterContext: + def __init__(self): + self._context = {} + + @property + def context(self) -> Dict[str, Any]: + return self._context + + @context.setter + def context(self, additional_context: Dict[str, Any]) -> None: + """Append key=value data as routing context""" + self._context.update(**additional_context) + + @context.deleter + def context(self): + """Resets routing context""" + self._context.clear() + + +class ResolverRegistry(BaseResolverRegistry): + def __init__(self): + self._resolvers: Dict[str, Dict[str, Any]] = {} + + @property + def resolvers(self) -> Dict[str, Dict[str, Any]]: + return self._resolvers + + @resolvers.setter + def resolvers(self, resolvers: dict) -> None: + self._resolvers.update(resolvers) + + def resolver(self, type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False): + """Registers the resolver for field_name + + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + + Return + ---------- + Dict + A dictionary with the resolver and if raise exception on error + """ + + def register(func): + logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") + self._resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} + return func + + return register + + def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: + """Find resolver based on type_name and field_name + + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + Return + ---------- + Optional[Dict] + A dictionary with the resolver and if raise exception on error + """ + + resolver = self._resolvers.get(f"{type_name}.{field_name}", self._resolvers.get(f"*.{field_name}")) + if not resolver: + return None + return resolver + + +class Router(BasePublic): + def __init__(self): + self._resolver_registry: BaseResolverRegistry = ResolverRegistry() + self._batch_resolver_registry: BaseResolverRegistry = ResolverRegistry() + self._batch_async_resolver_registry: BaseResolverRegistry = ResolverRegistry() + self._router_context: RouterContext = RouterContext() + + # Interfaces + def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) + + def batch_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + return self._batch_resolver_registry.resolver( + field_name=field_name, + type_name=type_name, + raise_on_error=raise_on_error, + ) + + def async_batch_resolver( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + return self._batch_async_resolver_registry.resolver( + field_name=field_name, + type_name=type_name, + raise_on_error=raise_on_error, + ) + + def append_context(self, **additional_context) -> None: + self._router_context.context = additional_context diff --git a/examples/event_handler_graphql/src/split_operation_append_context_module.py b/examples/event_handler_graphql/src/split_operation_append_context_module.py index d79d7cace44..f2ccc9ea7d7 100644 --- a/examples/event_handler_graphql/src/split_operation_append_context_module.py +++ b/examples/event_handler_graphql/src/split_operation_append_context_module.py @@ -1,7 +1,7 @@ from typing import List from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.event_handler.appsync import Router +from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.shared.types import TypedDict tracer = Tracer() diff --git a/examples/event_handler_graphql/src/split_operation_module.py b/examples/event_handler_graphql/src/split_operation_module.py index e4c7f978b73..5d7f9425c97 100644 --- a/examples/event_handler_graphql/src/split_operation_module.py +++ b/examples/event_handler_graphql/src/split_operation_module.py @@ -1,7 +1,7 @@ from typing import List from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.event_handler.appsync import Router +from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.shared.types import TypedDict tracer = Tracer() diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 18ec671901e..7a18e9479a2 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -4,8 +4,8 @@ import pytest from aws_lambda_powertools.event_handler import AppSyncResolver -from aws_lambda_powertools.event_handler.appsync import Router from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayload, ResolverNotFound +from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext from tests.functional.utils import load_event @@ -197,7 +197,7 @@ def test_resolver_include_mixed_resolver(): @router.batch_resolver(type_name="Query", field_name="listLocations") def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] + return f"get_locations#{name}#" + event.source["id"] @app.resolver(field_name="listLocations2") def get_locations2(name: str) -> str: From 50134fca0ff7c3b345d57f4ba5a9a5c21c1a6974 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 21 Feb 2024 17:06:55 +0100 Subject: [PATCH 31/75] refactor: rename BasePublic to BaseRouter --- .../event_handler/graphql_appsync/base.py | 11 +++-------- .../event_handler/graphql_appsync/router.py | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/base.py b/aws_lambda_powertools/event_handler/graphql_appsync/base.py index f3df4fabf16..775490bbc4f 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/base.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/base.py @@ -83,13 +83,8 @@ def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: raise NotImplementedError -class BasePublic(ABC): - """ - Abstract base class for public interface methods for resolver. - - This class outlines the methods that must be implemented by subclasses to manage resolvers and - context information. - """ +class BaseRouter(ABC): + """Abstract base class for Router (resolvers)""" @abstractmethod def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: @@ -224,7 +219,7 @@ def lambda_handler(event, context: LambdaContext) -> dict: @abstractmethod def append_context(self, **additional_context) -> None: """ - Append additional context information. + Appends context information available under any route. Parameters ----------- diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py index 983d577c4a2..15874aae9d2 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/router.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py @@ -1,7 +1,7 @@ import logging from typing import Any, Callable, Dict, Optional -from aws_lambda_powertools.event_handler.graphql_appsync.base import BasePublic, BaseResolverRegistry +from aws_lambda_powertools.event_handler.graphql_appsync.base import BaseResolverRegistry, BaseRouter logger = logging.getLogger(__name__) @@ -84,7 +84,7 @@ def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: return resolver -class Router(BasePublic): +class Router(BaseRouter): def __init__(self): self._resolver_registry: BaseResolverRegistry = ResolverRegistry() self._batch_resolver_registry: BaseResolverRegistry = ResolverRegistry() From 14635f54c6aadfe34bf84a1c6e88af0870b8e04d Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 21 Feb 2024 17:25:47 +0100 Subject: [PATCH 32/75] refactor: undo router context composition to reduce complexity and call stacks (getters) --- .../event_handler/appsync.py | 13 ++++---- .../event_handler/graphql_appsync/router.py | 32 ++++++------------- .../functional/event_handler/test_appsync.py | 12 +++---- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 262fd38b95c..d4efc2d3ff5 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -43,6 +43,8 @@ def __init__(self): Initialize a new instance of the AppSyncResolver. """ super().__init__() + self.context = {} # early init as customers might add context before event resolution + self.current_batch_event: List[AppSyncResolverEvent] = [] self.current_event: Optional[AppSyncResolverEvent] = None self.lambda_context: Optional[LambdaContext] = None @@ -139,7 +141,8 @@ def lambda_handler(event, context): if isinstance(event, list) else self._call_single_resolver(event=event, data_model=data_model) ) - del self._router_context.context + + self.clear_context() return response @@ -325,9 +328,10 @@ def include_router(self, router: "Router") -> None: """ # Merge app and router context - self._router_context.context = router._router_context.context + self.context.update(**router.context) + # use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx) - router._router_context._context = self._router_context.context + router.context = self.context self._resolver_registry.resolvers = router._resolver_registry.resolvers self._batch_resolver_registry.resolvers = router._batch_resolver_registry.resolvers @@ -360,6 +364,3 @@ def async_batch_resolver( type_name=type_name, raise_on_error=raise_on_error, ) - - def append_context(self, **additional_context) -> None: - self._router_context.context = additional_context diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py index 15874aae9d2..a78a3a13990 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/router.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py @@ -6,25 +6,6 @@ logger = logging.getLogger(__name__) -class RouterContext: - def __init__(self): - self._context = {} - - @property - def context(self) -> Dict[str, Any]: - return self._context - - @context.setter - def context(self, additional_context: Dict[str, Any]) -> None: - """Append key=value data as routing context""" - self._context.update(**additional_context) - - @context.deleter - def context(self): - """Resets routing context""" - self._context.clear() - - class ResolverRegistry(BaseResolverRegistry): def __init__(self): self._resolvers: Dict[str, Dict[str, Any]] = {} @@ -85,11 +66,13 @@ def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: class Router(BaseRouter): + context: dict + def __init__(self): + self.context = {} # early init as customers might add context before event resolution self._resolver_registry: BaseResolverRegistry = ResolverRegistry() self._batch_resolver_registry: BaseResolverRegistry = ResolverRegistry() self._batch_async_resolver_registry: BaseResolverRegistry = ResolverRegistry() - self._router_context: RouterContext = RouterContext() # Interfaces def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: @@ -119,5 +102,10 @@ def async_batch_resolver( raise_on_error=raise_on_error, ) - def append_context(self, **additional_context) -> None: - self._router_context.context = additional_context + def append_context(self, **additional_context): + """Append key=value data as routing context""" + self.context.update(**additional_context) + + def clear_context(self): + """Resets routing context""" + self.context.clear() diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 7a18e9479a2..b54eb9f68c7 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -241,13 +241,13 @@ def get_locations2(name: str) -> str: def test_append_context(): app = AppSyncResolver() app.append_context(is_admin=True) - assert app._router_context.context.get("is_admin") is True + assert app.context.get("is_admin") is True def test_router_append_context(): router = Router() router.append_context(is_admin=True) - assert router._router_context.context.get("is_admin") is True + assert router.context.get("is_admin") is True def test_route_context_is_cleared_after_resolve(): @@ -264,7 +264,7 @@ def get_locations(name: str): app.resolve(event, {}) # THEN context should be empty - assert app._router_context.context == {} + assert app.context == {} def test_router_has_access_to_app_context(): @@ -275,7 +275,7 @@ def test_router_has_access_to_app_context(): @router.resolver(type_name="Query", field_name="listLocations") def get_locations(name: str): - if router._router_context.context.get("is_admin"): + if router.context.get("is_admin"): return f"get_locations#{name}" app.include_router(router) @@ -286,7 +286,7 @@ def get_locations(name: str): # THEN assert ret == "get_locations#value" - assert router._router_context.context == {} + assert router.context == {} def test_include_router_merges_context(): @@ -300,7 +300,7 @@ def test_include_router_merges_context(): app.include_router(router) - assert app._router_context.context == router._router_context.context + assert app.context == router.context # Batch resolver tests From 774a72561fe488b51856f8e7bab1cc9877169809 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 21 Feb 2024 18:08:17 +0100 Subject: [PATCH 33/75] refactor: reduce abstractions, use explicit methods over assignments Signed-off-by: heitorlessa --- .../event_handler/appsync.py | 15 ++-- .../event_handler/graphql_appsync/base.py | 83 +------------------ .../event_handler/graphql_appsync/router.py | 55 ++++++------ .../functional/event_handler/test_appsync.py | 6 +- 4 files changed, 40 insertions(+), 119 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index d4efc2d3ff5..1c904decd4b 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -300,7 +300,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv type_name, field_name = self.current_batch_event[0].type_name, self.current_batch_event[0].field_name resolver = self._batch_resolver_registry.find_resolver(type_name, field_name) - async_resolver = self._batch_async_resolver_registry.find_resolver(type_name, field_name) + async_resolver = self._async_batch_resolver_registry.find_resolver(type_name, field_name) if resolver and async_resolver: warnings.warn( @@ -333,13 +333,12 @@ def include_router(self, router: "Router") -> None: # use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx) router.context = self.context - self._resolver_registry.resolvers = router._resolver_registry.resolvers - self._batch_resolver_registry.resolvers = router._batch_resolver_registry.resolvers - self._batch_async_resolver_registry.resolvers = router._batch_async_resolver_registry.resolvers + self._resolver_registry.merge(router._resolver_registry) + self._batch_resolver_registry.merge(router._batch_resolver_registry) + self._async_batch_resolver_registry.merge(router._async_batch_resolver_registry) - # Interfaces def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) + return self._resolver_registry.register(field_name=field_name, type_name=type_name) def batch_resolver( self, @@ -347,7 +346,7 @@ def batch_resolver( field_name: Optional[str] = None, raise_on_error: bool = False, ) -> Callable: - return self._batch_resolver_registry.resolver( + return self._batch_resolver_registry.register( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, @@ -359,7 +358,7 @@ def async_batch_resolver( field_name: Optional[str] = None, raise_on_error: bool = False, ) -> Callable: - return self._batch_async_resolver_registry.resolver( + return self._async_batch_resolver_registry.register( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/base.py b/aws_lambda_powertools/event_handler/graphql_appsync/base.py index 775490bbc4f..c95d300e5cf 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/base.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/base.py @@ -1,86 +1,5 @@ from abc import ABC, abstractmethod -from typing import Any, Callable, Dict, Optional - - -class BaseResolverRegistry(ABC): - """ - Abstract base class for a resolver registry. - - This class defines the interface for managing and retrieving resolvers - for various type and field combinations. - """ - - @property - @abstractmethod - def resolvers(self) -> Dict[str, Dict[str, Any]]: - """ - Get the dictionary of resolvers. - - Returns - ------- - dict - A dictionary containing resolver information. - """ - raise NotImplementedError - - @resolvers.setter - @abstractmethod - def resolvers(self, resolvers: dict) -> None: - """ - Set the dictionary of resolvers. - - Parameters - ---------- - resolvers: dict - A dictionary containing resolver information. - """ - raise NotImplementedError - - @abstractmethod - def resolver( - self, - type_name: str = "*", - field_name: Optional[str] = None, - raise_on_error: bool = False, - ): - """ - Retrieve a resolver function for a specific type and field. - - Parameters - ----------- - type_name: str - The name of the type. - field_name: Optional[str] - The name of the field (default is None). - raise_on_error: bool - A flag indicating whether to raise an error when processing batches - with failed items. Defaults to False, which means errors are handled without raising exceptions. - - Returns - ------- - Callable - The resolver function. - """ - raise NotImplementedError - - @abstractmethod - def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: - """ - Find a resolver function for a specific type and field. - - Parameters - ----------- - type_name: str - The name of the type. - field_name: str - The name of the field. - - Returns - ------- - Optional[Callable] - The resolver function. None if not found. - """ - raise NotImplementedError +from typing import Callable, Optional class BaseRouter(ABC): diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py index a78a3a13990..a5dc84b13f1 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/router.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py @@ -1,24 +1,21 @@ import logging from typing import Any, Callable, Dict, Optional -from aws_lambda_powertools.event_handler.graphql_appsync.base import BaseResolverRegistry, BaseRouter +from aws_lambda_powertools.event_handler.graphql_appsync.base import BaseRouter logger = logging.getLogger(__name__) -class ResolverRegistry(BaseResolverRegistry): +class ResolverRegistry: def __init__(self): - self._resolvers: Dict[str, Dict[str, Any]] = {} + self.resolvers: Dict[str, Dict[str, Any]] = {} - @property - def resolvers(self) -> Dict[str, Dict[str, Any]]: - return self._resolvers - - @resolvers.setter - def resolvers(self, resolvers: dict) -> None: - self._resolvers.update(resolvers) - - def resolver(self, type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False): + def register( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: """Registers the resolver for field_name Parameters @@ -37,12 +34,12 @@ def resolver(self, type_name: str = "*", field_name: Optional[str] = None, raise A dictionary with the resolver and if raise exception on error """ - def register(func): + def _register(func) -> Callable: logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") - self._resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} + self.resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} return func - return register + return _register def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: """Find resolver based on type_name and field_name @@ -59,10 +56,17 @@ def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: A dictionary with the resolver and if raise exception on error """ - resolver = self._resolvers.get(f"{type_name}.{field_name}", self._resolvers.get(f"*.{field_name}")) - if not resolver: - return None - return resolver + return self.resolvers.get(f"{type_name}.{field_name}", self.resolvers.get(f"*.{field_name}")) + + def merge(self, other_registry: "ResolverRegistry"): + """Update current registry with incoming registry + + Parameters + ---------- + other_registry : ResolverRegistry + Registry to merge from + """ + self.resolvers.update(**other_registry.resolvers) class Router(BaseRouter): @@ -70,13 +74,12 @@ class Router(BaseRouter): def __init__(self): self.context = {} # early init as customers might add context before event resolution - self._resolver_registry: BaseResolverRegistry = ResolverRegistry() - self._batch_resolver_registry: BaseResolverRegistry = ResolverRegistry() - self._batch_async_resolver_registry: BaseResolverRegistry = ResolverRegistry() + self._resolver_registry = ResolverRegistry() + self._batch_resolver_registry = ResolverRegistry() + self._async_batch_resolver_registry = ResolverRegistry() - # Interfaces def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: - return self._resolver_registry.resolver(field_name=field_name, type_name=type_name) + return self._resolver_registry.register(field_name=field_name, type_name=type_name) def batch_resolver( self, @@ -84,7 +87,7 @@ def batch_resolver( field_name: Optional[str] = None, raise_on_error: bool = False, ) -> Callable: - return self._batch_resolver_registry.resolver( + return self._batch_resolver_registry.register( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, @@ -96,7 +99,7 @@ def async_batch_resolver( field_name: Optional[str] = None, raise_on_error: bool = False, ) -> Callable: - return self._batch_async_resolver_registry.resolver( + return self._async_batch_resolver_registry.register( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index b54eb9f68c7..c940fca2a20 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -171,11 +171,11 @@ def test_resolver_include_resolver(): @router.resolver(type_name="Query", field_name="listLocations") def get_locations(name: str): - return "get_locations#" + name + return f"get_locations#{name}" @app.resolver(field_name="listLocations2") def get_locations2(name: str): - return "get_locations2#" + name + return f"get_locations2#{name}" app.include_router(router) @@ -201,7 +201,7 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: @app.resolver(field_name="listLocations2") def get_locations2(name: str) -> str: - return "get_locations2#" + name + return f"get_locations2#{name}" app.include_router(router) From e36dd7cc254ebdba8e3df385cdbf393adca02a5f Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 21 Feb 2024 18:13:40 +0100 Subject: [PATCH 34/75] refactor: move registry to a separate file; make it private --- .../graphql_appsync/_registry.py | 66 ++++++++++++++++++ .../event_handler/graphql_appsync/router.py | 69 +------------------ 2 files changed, 68 insertions(+), 67 deletions(-) create mode 100644 aws_lambda_powertools/event_handler/graphql_appsync/_registry.py diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py new file mode 100644 index 00000000000..acdf6e72cde --- /dev/null +++ b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py @@ -0,0 +1,66 @@ +import logging +from typing import Any, Callable, Dict, Optional + +logger = logging.getLogger(__name__) + + +class ResolverRegistry: + def __init__(self): + self.resolvers: Dict[str, Dict[str, Any]] = {} + + def register( + self, + type_name: str = "*", + field_name: Optional[str] = None, + raise_on_error: bool = False, + ) -> Callable: + """Registers the resolver for field_name + + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + raise_on_error: bool + A flag indicating whether to raise an error when processing batches + with failed items. Defaults to False, which means errors are handled without raising exceptions. + + Return + ---------- + Dict + A dictionary with the resolver and if raise exception on error + """ + + def _register(func) -> Callable: + logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") + self.resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} + return func + + return _register + + def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: + """Find resolver based on type_name and field_name + + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + Return + ---------- + Optional[Dict] + A dictionary with the resolver and if raise exception on error + """ + return self.resolvers.get(f"{type_name}.{field_name}", self.resolvers.get(f"*.{field_name}")) + + def merge(self, other_registry: "ResolverRegistry"): + """Update current registry with incoming registry + + Parameters + ---------- + other_registry : ResolverRegistry + Registry to merge from + """ + self.resolvers.update(**other_registry.resolvers) diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py index a5dc84b13f1..44d701ca4ab 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/router.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py @@ -1,73 +1,8 @@ -import logging -from typing import Any, Callable, Dict, Optional +from typing import Callable, Optional +from aws_lambda_powertools.event_handler.graphql_appsync._registry import ResolverRegistry from aws_lambda_powertools.event_handler.graphql_appsync.base import BaseRouter -logger = logging.getLogger(__name__) - - -class ResolverRegistry: - def __init__(self): - self.resolvers: Dict[str, Dict[str, Any]] = {} - - def register( - self, - type_name: str = "*", - field_name: Optional[str] = None, - raise_on_error: bool = False, - ) -> Callable: - """Registers the resolver for field_name - - Parameters - ---------- - type_name : str - Type name - field_name : str - Field name - raise_on_error: bool - A flag indicating whether to raise an error when processing batches - with failed items. Defaults to False, which means errors are handled without raising exceptions. - - Return - ---------- - Dict - A dictionary with the resolver and if raise exception on error - """ - - def _register(func) -> Callable: - logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") - self.resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} - return func - - return _register - - def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: - """Find resolver based on type_name and field_name - - Parameters - ---------- - type_name : str - Type name - field_name : str - Field name - Return - ---------- - Optional[Dict] - A dictionary with the resolver and if raise exception on error - """ - - return self.resolvers.get(f"{type_name}.{field_name}", self.resolvers.get(f"*.{field_name}")) - - def merge(self, other_registry: "ResolverRegistry"): - """Update current registry with incoming registry - - Parameters - ---------- - other_registry : ResolverRegistry - Registry to merge from - """ - self.resolvers.update(**other_registry.resolvers) - class Router(BaseRouter): context: dict From 3cddfff642895c388c824907edf1893fcaa176ed Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 21 Feb 2024 18:16:54 +0100 Subject: [PATCH 35/75] refactor: expand inline if for readability Signed-off-by: heitorlessa --- aws_lambda_powertools/event_handler/appsync.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 1c904decd4b..4498a054623 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -136,11 +136,10 @@ def lambda_handler(event, context): self.lambda_context = context - response = ( - self._call_batch_resolver(event=event, data_model=data_model) - if isinstance(event, list) - else self._call_single_resolver(event=event, data_model=data_model) - ) + if isinstance(event, list): + response = self._call_batch_resolver(event=event, data_model=data_model) + else: + response = self._call_single_resolver(event=event, data_model=data_model) self.clear_context() From 17015df11dc966585650d70085ef5a6931a4d63d Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 22 Feb 2024 13:59:20 +0100 Subject: [PATCH 36/75] refactor: short circuit upfront, complex after Renamed long var name to `event` to make it quicker to glance through. Simplified comments by removing redundant ones. --- .../event_handler/appsync.py | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 4498a054623..08623484eaa 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -180,23 +180,22 @@ def _call_sync_batch_resolver(self, sync_resolver: Callable, raise_on_error: boo A list of results corresponding to the resolved events. """ - # Check if we should raise errors or continue and append None in failed records - if not raise_on_error: - results: List = [] - for appconfig_event in self.current_batch_event: - try: - results.append(sync_resolver(event=appconfig_event, **appconfig_event.arguments)) - except Exception: - # If an error occurs and raise_error_on_failed_batch is False, - # append None to the results and continue with the next event. - results.append(None) - - return results - - return [ - sync_resolver(event=appconfig_event, **appconfig_event.arguments) - for appconfig_event in self.current_batch_event - ] + # Stop on first exception we encounter + if raise_on_error: + return [ + sync_resolver(event=appconfig_event, **appconfig_event.arguments) + for appconfig_event in self.current_batch_event + ] + + # By default, we gracefully append `None` for any records that failed processing + results = [] + for event in self.current_batch_event: + try: + results.append(sync_resolver(event=event, **event.arguments)) + except Exception: + results.append(None) + + return results async def _call_async_batch_resolver(self, async_resolver: Callable, raise_on_error: bool = False) -> List[Any]: """ @@ -274,7 +273,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv event : List[dict] Event data_model : Type[AppSyncResolverEvent] - Data_model to decode AppSync event, by default it is of AppSyncResolverEvent type or subclass of it + Data_model to decode AppSync event, by default AppSyncResolverEvent or a subclass Returns ------- @@ -314,8 +313,8 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv return asyncio.run( self._call_async_batch_resolver(async_resolver["func"], async_resolver["raise_on_error"]), ) - else: - raise ResolverNotFound(f"No resolver found for '{type_name}.{field_name}'") + + raise ResolverNotFound(f"No resolver found for '{type_name}.{field_name}'") def include_router(self, router: "Router") -> None: """Adds all resolvers defined in a router From 54d892b65afc88d030db7c52fcf89649fc93363b Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 22 Feb 2024 14:00:44 +0100 Subject: [PATCH 37/75] refactor: simplify arg name --- aws_lambda_powertools/event_handler/appsync.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 08623484eaa..a399d73c746 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -162,13 +162,13 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve raise ValueError(f"No resolver found for '{self.current_event.type_name}.{self.current_event.field_name}'") return resolver["func"](**self.current_event.arguments) - def _call_sync_batch_resolver(self, sync_resolver: Callable, raise_on_error: bool = False) -> List[Any]: + def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = False) -> List[Any]: """ Calls a synchronous batch resolver function for each event in the current batch. Parameters ---------- - sync_resolver: Callable + resolver: Callable The callable function to resolve events. raise_on_error: bool A flag indicating whether to raise an error when processing batches @@ -183,7 +183,7 @@ def _call_sync_batch_resolver(self, sync_resolver: Callable, raise_on_error: boo # Stop on first exception we encounter if raise_on_error: return [ - sync_resolver(event=appconfig_event, **appconfig_event.arguments) + resolver(event=appconfig_event, **appconfig_event.arguments) for appconfig_event in self.current_batch_event ] @@ -191,7 +191,7 @@ def _call_sync_batch_resolver(self, sync_resolver: Callable, raise_on_error: boo results = [] for event in self.current_batch_event: try: - results.append(sync_resolver(event=event, **event.arguments)) + results.append(resolver(event=event, **event.arguments)) except Exception: results.append(None) From 0ad3383d0a24cffa904adaf83b14cbf74da255ac Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 22 Feb 2024 14:13:03 +0100 Subject: [PATCH 38/75] refactor: add debug statements --- aws_lambda_powertools/event_handler/appsync.py | 9 ++++++++- .../event_handler/graphql_appsync/_registry.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index a399d73c746..6064ed25442 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,4 +1,5 @@ import asyncio +import logging import warnings from typing import Any, Callable, Dict, List, Optional, Type, Union @@ -7,6 +8,8 @@ from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext +logger = logging.getLogger(__name__) + class AppSyncResolver(Router): """ @@ -137,8 +140,10 @@ def lambda_handler(event, context): self.lambda_context = context if isinstance(event, list): + logger.debug("Received batch resolver event.") response = self._call_batch_resolver(event=event, data_model=data_model) else: + logger.debug("Received single resolver event.") response = self._call_single_resolver(event=event, data_model=data_model) self.clear_context() @@ -182,6 +187,7 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F # Stop on first exception we encounter if raise_on_error: + logger.debug("Graceful error handling disabled.") return [ resolver(event=appconfig_event, **appconfig_event.arguments) for appconfig_event in self.current_batch_event @@ -189,10 +195,11 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F # By default, we gracefully append `None` for any records that failed processing results = [] - for event in self.current_batch_event: + for idx, event in enumerate(self.current_batch_event): try: results.append(resolver(event=event, **event.arguments)) except Exception: + logger.debug(f"Failed to process event number {idx} from field '{event.info.field_name}'") results.append(None) return results diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py index acdf6e72cde..4a97325f7da 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py @@ -53,6 +53,7 @@ def find_resolver(self, type_name: str, field_name: str) -> Optional[Dict]: Optional[Dict] A dictionary with the resolver and if raise exception on error """ + logger.debug(f"Looking for resolver for type={type_name}, field={field_name}.") return self.resolvers.get(f"{type_name}.{field_name}", self.resolvers.get(f"*.{field_name}")) def merge(self, other_registry: "ResolverRegistry"): From eb340ec1e9006d17d24201e5168d20b8ced39064 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 22 Feb 2024 15:54:44 +0100 Subject: [PATCH 39/75] fix(docs): use .context instead of previous ._router.context --- .../src/split_operation_append_context_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/event_handler_graphql/src/split_operation_append_context_module.py b/examples/event_handler_graphql/src/split_operation_append_context_module.py index f2ccc9ea7d7..c12c22fb626 100644 --- a/examples/event_handler_graphql/src/split_operation_append_context_module.py +++ b/examples/event_handler_graphql/src/split_operation_append_context_module.py @@ -20,5 +20,5 @@ class Location(TypedDict, total=False): @router.resolver(field_name="locations") @tracer.capture_method def get_locations(name: str, description: str = "") -> List[Location]: # match GraphQL Query arguments - is_admin: bool = router._router_context.context.get("is_admin", False) + is_admin: bool = router.context.get("is_admin", False) return [{"name": name, "description": description}] if is_admin else [] From 732658251b4431423cef38ca8eee05e9497e84b8 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 22 Feb 2024 16:02:48 +0100 Subject: [PATCH 40/75] refactor: use kwargs for explicitness --- aws_lambda_powertools/event_handler/appsync.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 6064ed25442..966d8481238 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -204,13 +204,13 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F return results - async def _call_async_batch_resolver(self, async_resolver: Callable, raise_on_error: bool = False) -> List[Any]: + async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: bool = False) -> List[Any]: """ Asynchronously call a batch resolver for each event in the current batch. Parameters ---------- - async_resolver: Callable + resolver: Callable The asynchronous resolver function. raise_on_error: bool A flag indicating whether to raise an error when processing batches @@ -225,7 +225,7 @@ async def _call_async_batch_resolver(self, async_resolver: Callable, raise_on_er await asyncio.gather( *[ self._async_process_batch_event( - async_resolver, + resolver, appconfig_event, **appconfig_event.arguments, raise_on_error=raise_on_error, @@ -315,10 +315,13 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv ) if resolver: - return self._call_sync_batch_resolver(resolver["func"], resolver["raise_on_error"]) + return self._call_sync_batch_resolver(resolver=resolver["func"], raise_on_error=resolver["raise_on_error"]) elif async_resolver: return asyncio.run( - self._call_async_batch_resolver(async_resolver["func"], async_resolver["raise_on_error"]), + self._call_async_batch_resolver( + resolver=async_resolver["func"], + raise_on_error=async_resolver["raise_on_error"], + ), ) raise ResolverNotFound(f"No resolver found for '{type_name}.{field_name}'") From 26353faf6a9a9a5a6f1eafe1c26e8f43e1ef2ab4 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 11:08:14 +0100 Subject: [PATCH 41/75] refactor: use return_exceptions=True to reduce call stack Signed-off-by: heitorlessa --- .../event_handler/appsync.py | 54 ++++--------------- .../functional/event_handler/test_appsync.py | 4 +- 2 files changed, 11 insertions(+), 47 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 966d8481238..332d3da8011 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -221,56 +221,20 @@ async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: b List[Any] A list of results corresponding to the resolved events. """ - return list( - await asyncio.gather( - *[ - self._async_process_batch_event( - resolver, - appconfig_event, - **appconfig_event.arguments, - raise_on_error=raise_on_error, - ) - for appconfig_event in self.current_batch_event - ], - ), - ) - async def _async_process_batch_event( - self, - async_resolver: Callable, - appconfig_event: AppSyncResolverEvent, - raise_on_error: bool = False, - **kwargs, - ): - """ - Asynchronously process a batch event using the provided async_resolver. + response = [] - Parameters - ---------- - async_resolver: Callable - The asynchronous resolver function. - appconfig_event: AppSyncResolverEvent - The event to process. - raise_on_error: bool - A flag indicating whether to raise an error when processing batches - with failed items. Defaults to False, which means errors are handled without raising exceptions. - **kwargs - Additional keyword arguments to pass to the resolver. + # Prime coroutines + tasks = [resolver(event=e, **e.arguments) for e in self.current_batch_event] - Returns - ------- - Any - The result of the resolver function or None if an error occurs and self.raise_error_on_failed_batch is False - """ + if raise_on_error: + response.extend(await asyncio.gather(*tasks)) + return response - if not raise_on_error: - try: - return await async_resolver(event=appconfig_event, **kwargs) - except Exception: - return None + results = await asyncio.gather(*tasks, return_exceptions=True) + response.extend(None if isinstance(ret, Exception) else ret for ret in results) - # If raise_error_on_failed_batch is False, proceed without raising errors - return await async_resolver(event=appconfig_event, **kwargs) + return response def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolverEvent]) -> List[Any]: """Call batch event resolver for sync and async methods diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index c940fca2a20..31de5aec986 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -452,7 +452,7 @@ def test_async_resolve_batch_processing_with_raise_on_exception(): app = AppSyncResolver() @app.async_batch_resolver(field_name="listLocations", raise_on_error=True) - def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError # Call the implicit handler @@ -557,7 +557,7 @@ def test_resolve_async_batch_processing_without_exception(): app = AppSyncResolver() @app.async_batch_resolver(field_name="listLocations", raise_on_error=False) - def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 + async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError # Call the implicit handler From b27d4971301d9125225aedebe91643191720b745 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 11:19:13 +0100 Subject: [PATCH 42/75] chore: add notes on the beauty of return_exceptions --- aws_lambda_powertools/event_handler/appsync.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 332d3da8011..ca5c3fdb788 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -227,10 +227,16 @@ async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: b # Prime coroutines tasks = [resolver(event=e, **e.arguments) for e in self.current_batch_event] + # Aggregate results or raise at first error if raise_on_error: response.extend(await asyncio.gather(*tasks)) return response + # Aggregate results and exceptions, then filter them out + # Use `None` upon exception for graceful error handling at GraphQL engine level + # + # NOTE: asyncio.gather(return_exceptions=True) catches and includes exceptions in the results + # this will become useful when we support exception handling in AppSync resolver results = await asyncio.gather(*tasks, return_exceptions=True) response.extend(None if isinstance(ret, Exception) else ret for ret in results) From 2a1c785a75bcc1bab438766f3cf64a6512ca0424 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 11:39:46 +0100 Subject: [PATCH 43/75] refactor: append suffix in exceptions --- aws_lambda_powertools/event_handler/appsync.py | 12 +++++++----- .../event_handler/exceptions_appsync.py | 4 ++-- tests/functional/event_handler/test_appsync.py | 6 +++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index ca5c3fdb788..5f068394050 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -3,7 +3,7 @@ import warnings from typing import Any, Callable, Dict, List, Optional, Type, Union -from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayload, ResolverNotFound +from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayloadError, ResolverNotFoundError from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -259,17 +259,19 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv Raises ------ - InconsistentPayload: + InconsistentPayloadError: If all events in the batch do not have the same fieldName. - ResolverNotFound: + ResolverNotFoundError: If no resolver is found for the specified type and field. """ # All events in the batch must have the same fieldName field_names = {field_name["info"]["fieldName"] for field_name in event} if len(field_names) > 1: - raise InconsistentPayload(f"All events in the batch must have the same fieldName. Found: {field_names}") + raise InconsistentPayloadError( + f"All events in the batch must have the same fieldName. Found: {field_names}", + ) self.current_batch_event = [data_model(e) for e in event] type_name, field_name = self.current_batch_event[0].type_name, self.current_batch_event[0].field_name @@ -294,7 +296,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv ), ) - raise ResolverNotFound(f"No resolver found for '{type_name}.{field_name}'") + raise ResolverNotFoundError(f"No resolver found for '{type_name}.{field_name}'") def include_router(self, router: "Router") -> None: """Adds all resolvers defined in a router diff --git a/aws_lambda_powertools/event_handler/exceptions_appsync.py b/aws_lambda_powertools/event_handler/exceptions_appsync.py index 6708a73a83f..2f12b2849eb 100644 --- a/aws_lambda_powertools/event_handler/exceptions_appsync.py +++ b/aws_lambda_powertools/event_handler/exceptions_appsync.py @@ -1,10 +1,10 @@ -class ResolverNotFound(Exception): +class ResolverNotFoundError(Exception): """ When a resolver is not found during a lookup. """ -class InconsistentPayload(Exception): +class InconsistentPayloadError(Exception): """ When a payload is inconsistent or violates expected structure. """ diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 31de5aec986..12062523d79 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -4,7 +4,7 @@ import pytest from aws_lambda_powertools.event_handler import AppSyncResolver -from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayload, ResolverNotFound +from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayloadError, ResolverNotFoundError from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -607,7 +607,7 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: }, ] - with pytest.raises(InconsistentPayload): + with pytest.raises(InconsistentPayloadError): app.resolve(mock_event1, LambdaContext()) @@ -640,7 +640,7 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: app.include_router(router) # THEN must fail with ValueError - with pytest.raises(ResolverNotFound, match="No resolver found for.*"): + with pytest.raises(ResolverNotFoundError, match="No resolver found for.*"): app.resolve(mock_event1, LambdaContext()) From 98a8aff3e00ef0daa9c27c45b4881753acddd17b Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 11:45:02 +0100 Subject: [PATCH 44/75] chore: if over elif in short-circuit --- aws_lambda_powertools/event_handler/appsync.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 5f068394050..c999036b93a 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -288,7 +288,8 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv if resolver: return self._call_sync_batch_resolver(resolver=resolver["func"], raise_on_error=resolver["raise_on_error"]) - elif async_resolver: + + if async_resolver: return asyncio.run( self._call_async_batch_resolver( resolver=async_resolver["func"], From 32fd640d608a1a75ea5ae0ad74e791bd68343ae0 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 12:06:45 +0100 Subject: [PATCH 45/75] chore: improve logging; glad I learned this new f-string trick --- aws_lambda_powertools/event_handler/appsync.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index c999036b93a..6c5503fba95 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -139,11 +139,12 @@ def lambda_handler(event, context): self.lambda_context = context - if isinstance(event, list): - logger.debug("Received batch resolver event.") + is_batch_event = isinstance(event, list) + logger.debug(f"Resolving event: {is_batch_event=}") + + if is_batch_event: response = self._call_batch_resolver(event=event, data_model=data_model) else: - logger.debug("Received single resolver event.") response = self._call_single_resolver(event=event, data_model=data_model) self.clear_context() @@ -187,7 +188,6 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F # Stop on first exception we encounter if raise_on_error: - logger.debug("Graceful error handling disabled.") return [ resolver(event=appconfig_event, **appconfig_event.arguments) for appconfig_event in self.current_batch_event @@ -286,10 +286,14 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv stacklevel=2, ) + logger.debug(f'Graceful error handling: {resolver["raise_on_error"]}') + if resolver: + logger.debug(f"Found sync resolver. {resolver=}, {field_name=}") return self._call_sync_batch_resolver(resolver=resolver["func"], raise_on_error=resolver["raise_on_error"]) if async_resolver: + logger.debug(f"Found async resolver. {resolver=}, {field_name=}") return asyncio.run( self._call_async_batch_resolver( resolver=async_resolver["func"], @@ -309,11 +313,13 @@ def include_router(self, router: "Router") -> None: """ # Merge app and router context + logger.debug("Merging router and app context") self.context.update(**router.context) # use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx) router.context = self.context + logger.debug("Merging router resolver registries") self._resolver_registry.merge(router._resolver_registry) self._batch_resolver_registry.merge(router._batch_resolver_registry) self._async_batch_resolver_registry.merge(router._async_batch_resolver_registry) From de823a620ccd68e3996c927aa5d688b51b7f6189 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 14:29:07 +0100 Subject: [PATCH 46/75] chore: fix debug statement location due to null resolvers --- aws_lambda_powertools/event_handler/appsync.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 6c5503fba95..a0783271447 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -248,7 +248,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv Parameters ---------- event : List[dict] - Event + Batch event data_model : Type[AppSyncResolverEvent] Data_model to decode AppSync event, by default AppSyncResolverEvent or a subclass @@ -260,10 +260,10 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv Raises ------ InconsistentPayloadError: - If all events in the batch do not have the same fieldName. + When all events in the batch do not have the same fieldName. ResolverNotFoundError: - If no resolver is found for the specified type and field. + When no resolver is found for the specified type and field. """ # All events in the batch must have the same fieldName @@ -286,14 +286,12 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv stacklevel=2, ) - logger.debug(f'Graceful error handling: {resolver["raise_on_error"]}') - if resolver: - logger.debug(f"Found sync resolver. {resolver=}, {field_name=}") + logger.debug(f'Found sync resolver. {resolver=}, {field_name=}, {resolver["raise_on_error"]=}') return self._call_sync_batch_resolver(resolver=resolver["func"], raise_on_error=resolver["raise_on_error"]) if async_resolver: - logger.debug(f"Found async resolver. {resolver=}, {field_name=}") + logger.debug(f'Found async resolver. {resolver=}, {field_name=} {resolver["raise_on_error"]=}') return asyncio.run( self._call_async_batch_resolver( resolver=async_resolver["func"], From 16987785ad065d2bfae1e3625a58c1ce21e31ca9 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 14:53:18 +0100 Subject: [PATCH 47/75] revert: debug graceful error flag due to non-determinism async Signed-off-by: heitorlessa --- aws_lambda_powertools/event_handler/appsync.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index a0783271447..49d56d4cd2b 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -186,6 +186,8 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F A list of results corresponding to the resolved events. """ + logger.debug(f"Graceful error handling flag {raise_on_error=}") + # Stop on first exception we encounter if raise_on_error: return [ @@ -222,6 +224,7 @@ async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: b A list of results corresponding to the resolved events. """ + logger.debug(f"Graceful error handling flag {raise_on_error=}") response = [] # Prime coroutines @@ -287,11 +290,11 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv ) if resolver: - logger.debug(f'Found sync resolver. {resolver=}, {field_name=}, {resolver["raise_on_error"]=}') + logger.debug(f"Found sync resolver. {resolver=}, {field_name=}") return self._call_sync_batch_resolver(resolver=resolver["func"], raise_on_error=resolver["raise_on_error"]) if async_resolver: - logger.debug(f'Found async resolver. {resolver=}, {field_name=} {resolver["raise_on_error"]=}') + logger.debug(f"Found async resolver. {resolver=}, {field_name=}") return asyncio.run( self._call_async_batch_resolver( resolver=async_resolver["func"], From 3e036f91acf18743549b6d7506e9522799e3dd3e Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 15:10:07 +0100 Subject: [PATCH 48/75] revert: debug stmt due to mypy; moving elsewhere Signed-off-by: heitorlessa --- aws_lambda_powertools/event_handler/appsync.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 49d56d4cd2b..5a95f2eb923 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -139,10 +139,7 @@ def lambda_handler(event, context): self.lambda_context = context - is_batch_event = isinstance(event, list) - logger.debug(f"Resolving event: {is_batch_event=}") - - if is_batch_event: + if isinstance(event, list): response = self._call_batch_resolver(event=event, data_model=data_model) else: response = self._call_single_resolver(event=event, data_model=data_model) @@ -162,6 +159,8 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve Data_model to decode AppSync event, by default it is of AppSyncResolverEvent type or subclass of it """ + logger.debug("Processing direct resolver event") + self.current_event = data_model(event) resolver = self._resolver_registry.find_resolver(self.current_event.type_name, self.current_event.field_name) if not resolver: @@ -268,6 +267,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv ResolverNotFoundError: When no resolver is found for the specified type and field. """ + logger.debug("Processing batch resolver event") # All events in the batch must have the same fieldName field_names = {field_name["info"]["fieldName"] for field_name in event} From 01251bcad7ad6d6f6265f977d0314094eff8a89e Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 15:20:34 +0100 Subject: [PATCH 49/75] docs: docstring resolver (tech debt) Signed-off-by: heitorlessa --- .../event_handler/appsync.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 5a95f2eb923..3d2f0d900bb 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -326,6 +326,47 @@ def include_router(self, router: "Router") -> None: self._async_batch_resolver_registry.merge(router._async_batch_resolver_registry) def resolver(self, type_name: str = "*", field_name: Optional[str] = None) -> Callable: + """Registers direct resolver function for GraphQL type and field name. + + Parameters + ---------- + type_name : str, optional + GraphQL type e.g., Query, Mutation, by default "*" meaning any + field_name : Optional[str], optional + GraphQL field e.g., getTodo, createTodo, by default None + + Returns + ------- + Callable + Registered resolver + + Example + ------- + + ```python + from aws_lambda_powertools.event_handler import AppSyncResolver + + from typing import TypedDict + + app = AppSyncResolver() + + class Todo(TypedDict, total=False): + id: str + userId: str + title: str + completed: bool + + # resolve any GraphQL `getTodo` queries + # arguments are injected as function arguments as-is + @app.resolver(type_name="Query", field_name="getTodo") + def get_todo(id: str = "", status: str = "open") -> Todo: + logger.info(f"Fetching Todo {id}") + todos: Response = requests.get(f"https://jsonplaceholder.typicode.com/todos/{id}") + todos.raise_for_status() + + return todos.json() + ``` + """ return self._resolver_registry.register(field_name=field_name, type_name=type_name) def batch_resolver( From d2807421c4b7f3f1f3a5def7eb18a6f6d797fd84 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 15:33:01 +0100 Subject: [PATCH 50/75] docs: minimal batch_resolver docstring Signed-off-by: heitorlessa --- .../event_handler/appsync.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 3d2f0d900bb..52ea7c98ec2 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -375,6 +375,25 @@ def batch_resolver( field_name: Optional[str] = None, raise_on_error: bool = False, ) -> Callable: + """Registers batch resolver function for GraphQL type and field name. + + By default, we handle errors gracefully by returning `None`. If you want + to short-circuit and fail the entire batch use `raise_on_error=True`. + + Parameters + ---------- + type_name : str, optional + GraphQL type e.g., Query, Mutation, by default "*" meaning any + field_name : Optional[str], optional + GraphQL field e.g., getTodo, createTodo, by default None + raise_on_error : bool, optional + Whether to fail entire batch upon error, or handle errors gracefully (None), by default False + + Returns + ------- + Callable + Registered resolver + """ return self._batch_resolver_registry.register( field_name=field_name, type_name=type_name, From 507d85465558ae9bb9b337989997e0563a5ab6a8 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 23 Feb 2024 15:35:33 +0100 Subject: [PATCH 51/75] chore: complete resolver docstring Signed-off-by: heitorlessa --- aws_lambda_powertools/event_handler/appsync.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 52ea7c98ec2..804b2f35785 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -360,11 +360,13 @@ class Todo(TypedDict, total=False): # arguments are injected as function arguments as-is @app.resolver(type_name="Query", field_name="getTodo") def get_todo(id: str = "", status: str = "open") -> Todo: - logger.info(f"Fetching Todo {id}") todos: Response = requests.get(f"https://jsonplaceholder.typicode.com/todos/{id}") todos.raise_for_status() return todos.json() + + def lambda_handler(event, context): + return app.resolve(event, context) ``` """ return self._resolver_registry.register(field_name=field_name, type_name=type_name) From a805ac33a4da047df15e1376cde087e5cbece497 Mon Sep 17 00:00:00 2001 From: Cavalcante Damascena Date: Tue, 27 Feb 2024 08:33:03 -0300 Subject: [PATCH 52/75] Removing payload exception --- .../event_handler/appsync.py | 9 +--- .../event_handler/exceptions_appsync.py | 10 ----- .../graphql_appsync/exceptions.py | 4 ++ .../functional/event_handler/test_appsync.py | 45 +------------------ 4 files changed, 6 insertions(+), 62 deletions(-) delete mode 100644 aws_lambda_powertools/event_handler/exceptions_appsync.py create mode 100644 aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 804b2f35785..ce9a2cffad8 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -3,7 +3,7 @@ import warnings from typing import Any, Callable, Dict, List, Optional, Type, Union -from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayloadError, ResolverNotFoundError +from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import ResolverNotFoundError from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -269,13 +269,6 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv """ logger.debug("Processing batch resolver event") - # All events in the batch must have the same fieldName - field_names = {field_name["info"]["fieldName"] for field_name in event} - if len(field_names) > 1: - raise InconsistentPayloadError( - f"All events in the batch must have the same fieldName. Found: {field_names}", - ) - self.current_batch_event = [data_model(e) for e in event] type_name, field_name = self.current_batch_event[0].type_name, self.current_batch_event[0].field_name diff --git a/aws_lambda_powertools/event_handler/exceptions_appsync.py b/aws_lambda_powertools/event_handler/exceptions_appsync.py deleted file mode 100644 index 2f12b2849eb..00000000000 --- a/aws_lambda_powertools/event_handler/exceptions_appsync.py +++ /dev/null @@ -1,10 +0,0 @@ -class ResolverNotFoundError(Exception): - """ - When a resolver is not found during a lookup. - """ - - -class InconsistentPayloadError(Exception): - """ - When a payload is inconsistent or violates expected structure. - """ diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py b/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py new file mode 100644 index 00000000000..2be78bca69d --- /dev/null +++ b/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py @@ -0,0 +1,4 @@ +class ResolverNotFoundError(Exception): + """ + When a resolver is not found during a lookup. + """ diff --git a/tests/functional/event_handler/test_appsync.py b/tests/functional/event_handler/test_appsync.py index 12062523d79..1b248a2f848 100644 --- a/tests/functional/event_handler/test_appsync.py +++ b/tests/functional/event_handler/test_appsync.py @@ -4,7 +4,7 @@ import pytest from aws_lambda_powertools.event_handler import AppSyncResolver -from aws_lambda_powertools.event_handler.exceptions_appsync import InconsistentPayloadError, ResolverNotFoundError +from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import ResolverNotFoundError from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -568,49 +568,6 @@ async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # no assert not app.current_event -def test_resolver_batch_resolver_many_fields_with_different_name(): - # GIVEN - app = AppSyncResolver() - router = Router() - - @router.batch_resolver(type_name="Query", field_name="listLocations") - def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] - - app.include_router(router) - - # WHEN - mock_event1 = [ - { - "typeName": "Query", - "info": { - "fieldName": "listLocations", - "parentTypeName": "Query", - }, - "fieldName": "listLocations", - "arguments": {"name": "value"}, - "source": { - "id": "1", - }, - }, - { - "typeName": "Query", - "info": { - "fieldName": "listLocationsDifferent", - "parentTypeName": "Query", - }, - "fieldName": "listLocations", - "arguments": {"name": "value"}, - "source": { - "id": "1", - }, - }, - ] - - with pytest.raises(InconsistentPayloadError): - app.resolve(mock_event1, LambdaContext()) - - def test_resolver_batch_with_resolver_not_found(): # GIVEN a AppSyncResolver app = AppSyncResolver() From 85921940f8596454de91a49d73418dc972d68d75 Mon Sep 17 00:00:00 2001 From: Cavalcante Damascena Date: Tue, 27 Feb 2024 08:43:58 -0300 Subject: [PATCH 53/75] Updating poetry --- poetry.lock | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 349177b346a..b7c19b45753 100644 --- a/poetry.lock +++ b/poetry.lock @@ -160,7 +160,6 @@ typeguard = ">=2.13.3,<2.14.0" name = "aws-cdk-aws-appsync-alpha" version = "2.59.0a0" description = "The CDK Construct Library for AWS::AppSync" -category = "dev" optional = false python-versions = "~=3.7" files = [ @@ -349,17 +348,17 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.34.49" +version = "1.34.50" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.8" files = [ - {file = "boto3-1.34.49-py3-none-any.whl", hash = "sha256:ce8d1de03024f52a1810e8d71ad4dba3a5b9bb48b35567191500e3432a9130b4"}, - {file = "boto3-1.34.49.tar.gz", hash = "sha256:96b9dc85ce8d52619b56ca7b1ac1423eaf0af5ce132904bcc8aa81396eec2abf"}, + {file = "boto3-1.34.50-py3-none-any.whl", hash = "sha256:8d709365231234bc4f0ca98fdf33a25eeebf78072853c6aa3d259f0f5cf09877"}, + {file = "boto3-1.34.50.tar.gz", hash = "sha256:290952be7899560039cb0042e8a2354f61a7dead0d0ca8bea6ba901930df0468"}, ] [package.dependencies] -botocore = ">=1.34.49,<1.35.0" +botocore = ">=1.34.50,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -368,13 +367,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.49" +version = "1.34.50" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.8" files = [ - {file = "botocore-1.34.49-py3-none-any.whl", hash = "sha256:4ed9d7603a04b5bb5bd5de63b513bc2c8a7e8b1cd0088229c5ceb461161f43b6"}, - {file = "botocore-1.34.49.tar.gz", hash = "sha256:d89410bc60673eaff1699f3f1fdcb0e3a5e1f7a6a048c0d88c3ce5c3549433ec"}, + {file = "botocore-1.34.50-py3-none-any.whl", hash = "sha256:fda510559dbe796eefdb59561cc81be1b99afba3dee53fd23db9a3d587adc0ab"}, + {file = "botocore-1.34.50.tar.gz", hash = "sha256:33ab82cb96c4bb684f0dbafb071808e4817d83debc88b223e7d988256370c6d7"}, ] [package.dependencies] @@ -2959,13 +2958,13 @@ pbr = "*" [[package]] name = "sentry-sdk" -version = "1.40.5" +version = "1.40.6" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, - {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, + {file = "sentry-sdk-1.40.6.tar.gz", hash = "sha256:f143f3fb4bb57c90abef6e2ad06b5f6f02b2ca13e4060ec5c0549c7a9ccce3fa"}, + {file = "sentry_sdk-1.40.6-py2.py3-none-any.whl", hash = "sha256:becda09660df63e55f307570e9817c664392655a7328bbc414b507e9cb874c67"}, ] [package.dependencies] @@ -2991,7 +2990,7 @@ huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] +pure-eval = ["asttokens", "executing", "pure_eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -3451,4 +3450,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0.0" -content-hash = "ff8aabf1c8d72613c8b43d6f85bd1127acfbc2f4d6ad7cc352e4f87ebaf6222a" +content-hash = "fbafb81f335cd93888c7ff530b6d1802bcd9e1013899f444a103aa27a0f234f8" From e14c73f393daa929d4757e2ed91102e93c5198ad Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 14 Mar 2024 23:03:36 +0000 Subject: [PATCH 54/75] Merging from develop --- poetry.lock | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index ef6739c6a24..84067adda14 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "anyio" @@ -1301,6 +1301,17 @@ files = [ {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a3a6a2fbbe7550ffe52d151cf76065e6b89cfb3e9d0463e49a7e322a25d0426"}, {file = "ijson-3.2.3-cp311-cp311-win32.whl", hash = "sha256:6a4db2f7fb9acfb855c9ae1aae602e4648dd1f88804a0d5cfb78c3639bcf156c"}, {file = "ijson-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccd6be56335cbb845f3d3021b1766299c056c70c4c9165fb2fbe2d62258bae3f"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:055b71bbc37af5c3c5861afe789e15211d2d3d06ac51ee5a647adf4def19c0ea"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c075a547de32f265a5dd139ab2035900fef6653951628862e5cdce0d101af557"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:457f8a5fc559478ac6b06b6d37ebacb4811f8c5156e997f0d87d708b0d8ab2ae"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9788f0c915351f41f0e69ec2618b81ebfcf9f13d9d67c6d404c7f5afda3e4afb"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa234ab7a6a33ed51494d9d2197fb96296f9217ecae57f5551a55589091e7853"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd0dc5da4f9dc6d12ab6e8e0c57d8b41d3c8f9ceed31a99dae7b2baf9ea769a"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c6beb80df19713e39e68dc5c337b5c76d36ccf69c30b79034634e5e4c14d6904"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a2973ce57afb142d96f35a14e9cfec08308ef178a2c76b8b5e1e98f3960438bf"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:105c314fd624e81ed20f925271ec506523b8dd236589ab6c0208b8707d652a0e"}, + {file = "ijson-3.2.3-cp312-cp312-win32.whl", hash = "sha256:ac44781de5e901ce8339352bb5594fcb3b94ced315a34dbe840b4cff3450e23b"}, + {file = "ijson-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:0567e8c833825b119e74e10a7c29761dc65fcd155f5d4cb10f9d3b8916ef9912"}, {file = "ijson-3.2.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eeb286639649fb6bed37997a5e30eefcacddac79476d24128348ec890b2a0ccb"}, {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:396338a655fb9af4ac59dd09c189885b51fa0eefc84d35408662031023c110d1"}, {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e0243d166d11a2a47c17c7e885debf3b19ed136be2af1f5d1c34212850236ac"}, @@ -2590,6 +2601,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2597,8 +2609,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2615,6 +2635,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2622,6 +2643,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3374,6 +3396,17 @@ files = [ {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, + {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, + {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, + {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, + {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, + {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, ] [package.extras] From 9ed003e370a5480e70996e0e119a84ea1dd68dfd Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 24 Jun 2024 14:00:34 +0100 Subject: [PATCH 55/75] Addressing Heitor's feedback --- .../event_handler/appsync.py | 2 + .../required_dependencies/test_appsync.py | 155 +++++++++++++++--- 2 files changed, 133 insertions(+), 24 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index ce9a2cffad8..535402366b1 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -7,6 +7,7 @@ from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.warnings import PowertoolsUserWarning logger = logging.getLogger(__name__) @@ -280,6 +281,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv f"Both synchronous and asynchronous resolvers found for the same event and field." f"The synchronous resolver takes precedence. Executing: {resolver['func'].__name__}", stacklevel=2, + category=PowertoolsUserWarning, ) if resolver: diff --git a/tests/functional/event_handler/required_dependencies/test_appsync.py b/tests/functional/event_handler/required_dependencies/test_appsync.py index 1b248a2f848..dae9b0e2a97 100644 --- a/tests/functional/event_handler/required_dependencies/test_appsync.py +++ b/tests/functional/event_handler/required_dependencies/test_appsync.py @@ -8,6 +8,7 @@ from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.warnings import PowertoolsUserWarning from tests.functional.utils import load_event @@ -303,8 +304,106 @@ def test_include_router_merges_context(): assert app.context == router.context +def test_resolve_batch_processing_with_related_events(): + # GIVEN An event with multiple requests to fetch related posts for different post IDs. + event = [ + { + "arguments": {}, + "identity": "None", + "source": { + "post_id": "3", + "title": "Third book", + }, + "info": { + "selectionSetList": [ + "title", + ], + "selectionSetGraphQL": "{\n title\n}", + "fieldName": "relatedPosts", + "parentTypeName": "Post", + }, + }, + { + "arguments": {}, + "identity": "None", + "source": { + "post_id": "4", + "title": "Fifth book", + }, + "info": { + "selectionSetList": [ + "title", + ], + "selectionSetGraphQL": "{\n title\n}", + "fieldName": "relatedPosts", + "parentTypeName": "Post", + }, + }, + { + "arguments": {}, + "identity": "None", + "source": { + "post_id": "1", + "title": "First book", + }, + "info": { + "selectionSetList": [ + "title", + ], + "selectionSetGraphQL": "{\n title\n}", + "fieldName": "relatedPosts", + "parentTypeName": "Post", + }, + }, + ] + + # GIVEN A dictionary of posts and a dictionary of related posts. + posts = { + "1": { + "post_id": "1", + "title": "First book", + }, + "2": { + "post_id": "2", + "title": "Second book", + }, + "3": { + "post_id": "3", + "title": "Third book", + }, + "4": { + "post_id": "4", + "title": "Fourth book", + }, + } + + posts_related = { + "1": [posts["2"]], + "2": [posts["3"], posts["4"], posts["1"]], + "3": [posts["2"], posts["1"]], + "4": [posts["3"], posts["1"]], + } + + app = AppSyncResolver() + + @app.batch_resolver(type_name="Post", field_name="relatedPosts") + def related_posts(event: AppSyncResolverEvent) -> Optional[list]: + return posts_related[event.source["post_id"]] + + # WHEN related_posts function, which is the batch resolver, is called with the event. + result = app.resolve(event, LambdaContext()) + + # THEN the result must be a list of related posts + assert result == [ + posts_related["3"], + posts_related["4"], + posts_related["1"], + ] + + # Batch resolver tests -def test_resolve_batch_processing(): +def test_resolve_batch_processing_with_simple_queries(): + # GIVEN a list of events representing GraphQL queries for listing locations event = [ { "typeName": "Query", @@ -346,11 +445,12 @@ def test_resolve_batch_processing(): app = AppSyncResolver() + # WHEN the batch resolver for the listLocations field is defined @app.batch_resolver(field_name="listLocations") def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 return event.source["id"] if event.source else None - # Call the implicit handler + # THEN the resolver should correctly process the batch of queries result = app.resolve(event, LambdaContext()) assert result == [appsync_event["source"]["id"] for appsync_event in event] @@ -359,6 +459,7 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0 def test_resolve_batch_processing_with_raise_on_exception(): + # GIVEN a list of events representing GraphQL queries for listing locations event = [ { "typeName": "Query", @@ -400,16 +501,18 @@ def test_resolve_batch_processing_with_raise_on_exception(): app = AppSyncResolver() + # WHEN the sync batch resolver for the 'listLocations' field is defined with raise_on_error=True @app.batch_resolver(field_name="listLocations", raise_on_error=True) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError - # Call the implicit handler + # THEN the resolver should raise a RuntimeError when processing the batch of queries with pytest.raises(RuntimeError): app.resolve(event, LambdaContext()) def test_async_resolve_batch_processing_with_raise_on_exception(): + # GIVEN a list of events representing GraphQL queries for listing locations event = [ { "typeName": "Query", @@ -451,11 +554,12 @@ def test_async_resolve_batch_processing_with_raise_on_exception(): app = AppSyncResolver() + # WHEN the async batch resolver for the 'listLocations' field is defined with raise_on_error=True @app.async_batch_resolver(field_name="listLocations", raise_on_error=True) async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError - # Call the implicit handler + # THEN the resolver should raise a RuntimeError when processing the batch of queries with pytest.raises(RuntimeError): app.resolve(event, LambdaContext()) @@ -515,6 +619,7 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0 def test_resolve_async_batch_processing_without_exception(): + # GIVEN a list of events representing GraphQL queries for listing locations event = [ { "typeName": "Query", @@ -556,16 +661,16 @@ def test_resolve_async_batch_processing_without_exception(): app = AppSyncResolver() + # WHEN the batch resolver for the 'listLocations' field is defined with raise_on_error=False @app.async_batch_resolver(field_name="listLocations", raise_on_error=False) async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError - # Call the implicit handler result = app.resolve(event, LambdaContext()) - assert result == [None, None, None] - assert app.current_batch_event and len(app.current_batch_event) == len(event) - assert not app.current_event + # THEN the resolver should return None for each event in the batch + assert len(app.current_batch_event) == len(event) + assert result == [None, None, None] def test_resolver_batch_with_resolver_not_found(): @@ -596,7 +701,7 @@ def get_locations(event: AppSyncResolverEvent, name: str) -> str: app.include_router(router) - # THEN must fail with ValueError + # THEN must fail with ResolverNotFoundError with pytest.raises(ResolverNotFoundError, match="No resolver found for.*"): app.resolve(mock_event1, LambdaContext()) @@ -633,27 +738,27 @@ async def get_locations_async(event: AppSyncResolverEvent, name: str) -> str: app.include_router(router) - # THEN must fail with ValueError - with pytest.warns(UserWarning, match="Both synchronous and asynchronous resolvers*"): + # THEN must raise a PowertoolsUserWarning + with pytest.warns(PowertoolsUserWarning, match="Both synchronous and asynchronous resolvers*"): app.resolve(mock_event1, LambdaContext()) -def test_resolver_include_batch_resolver(): - # GIVEN +def test_batch_resolver_with_router(): + # GIVEN an AppSyncResolver and a Router instance app = AppSyncResolver() router = Router() @router.batch_resolver(type_name="Query", field_name="listLocations") def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] + return f"get_locations#{name}#" + event.source["id"] - @app.batch_resolver(field_name="listLocations2") + @router.batch_resolver(field_name="listLocations2") def get_locations2(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations2#" + name + "#" + event.source["id"] + return f"get_locations2#{name}#" + event.source["id"] + # WHEN we include the routes app.include_router(router) - # WHEN mock_event1 = [ { "typeName": "Query", @@ -685,12 +790,13 @@ def get_locations2(event: AppSyncResolverEvent, name: str) -> str: result1 = app.resolve(mock_event1, LambdaContext()) result2 = app.resolve(mock_event2, LambdaContext()) - # THEN + # THEN the resolvers should return the expected results assert result1 == ["get_locations#value#1"] assert result2 == ["get_locations2#value#2"] def test_resolve_async_batch_processing(): + # GIVEN a list of events representing GraphQL queries for listing locations event = [ { "typeName": "Query", @@ -732,11 +838,12 @@ def test_resolve_async_batch_processing(): app = AppSyncResolver() + # WHEN the async batch resolver for the 'listLocations' field is defined @app.async_batch_resolver(field_name="listLocations") async def create_something(event: AppSyncResolverEvent) -> Optional[list]: return event.source["id"] if event.source else None - # Call the implicit handler + # THEN the resolver should correctly process the batch of queries asynchronously result = app.resolve(event, LambdaContext()) assert result == [appsync_event["source"]["id"] for appsync_event in event] @@ -744,7 +851,7 @@ async def create_something(event: AppSyncResolverEvent) -> Optional[list]: def test_resolve_async_batch_and_sync_singular_processing(): - # GIVEN + # GIVEN a router with an async batch resolver for 'listLocations' and a sync singular resolver for 'listLocation' app = AppSyncResolver() router = Router() @@ -758,7 +865,7 @@ def get_location(name: str) -> str: app.include_router(router) - # WHEN + # WHEN resolving a batch of events for async 'listLocations' and a singular event for 'listLocation' mock_event1 = [ { "typeName": "Query", @@ -778,7 +885,7 @@ def get_location(name: str) -> str: result1 = app.resolve(mock_event1, LambdaContext()) result2 = app.resolve(mock_event2, LambdaContext()) - # THEN + # THEN the resolvers should return the expected results assert result1 == ["get_locations#value#1"] assert result2 == "get_location#value" @@ -790,11 +897,11 @@ def test_async_resolver_include_batch_resolver(): @router.async_batch_resolver(type_name="Query", field_name="listLocations") async def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] + return f"get_locations#{name}#" + event.source["id"] @app.async_batch_resolver(field_name="listLocations2") async def get_locations2(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations2#" + name + "#" + event.source["id"] + return f"get_locations2#{name}#" + event.source["id"] app.include_router(router) From 84c3590b09798fe55ee7a51cbcaeb249321c6028 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 24 Jun 2024 22:51:39 +0100 Subject: [PATCH 56/75] Refactoring to support aggregate events --- .../event_handler/appsync.py | 62 +- .../graphql_appsync/_registry.py | 11 +- .../event_handler/graphql_appsync/base.py | 10 + .../graphql_appsync/exceptions.py | 6 + .../event_handler/graphql_appsync/router.py | 4 + .../files/schema.graphql | 4 +- .../handlers/appsync_resolver_handler.py | 17 +- .../event_handler_appsync/infrastructure.py | 14 + .../test_appsync_resolvers.py | 40 +- .../required_dependencies/appsync/__init__.py | 0 .../test_appsync_batch_resolvers.py} | 531 +++++++----------- .../appsync/test_appsync_single_resolvers.py | 253 +++++++++ 12 files changed, 619 insertions(+), 333 deletions(-) create mode 100644 tests/functional/event_handler/required_dependencies/appsync/__init__.py rename tests/functional/event_handler/required_dependencies/{test_appsync.py => appsync/test_appsync_batch_resolvers.py} (70%) create mode 100644 tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 535402366b1..ab2a808189b 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -3,7 +3,7 @@ import warnings from typing import Any, Callable, Dict, List, Optional, Type, Union -from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import ResolverNotFoundError +from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import InvalidBatchResponse, ResolverNotFoundError from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext @@ -168,7 +168,12 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve raise ValueError(f"No resolver found for '{self.current_event.type_name}.{self.current_event.field_name}'") return resolver["func"](**self.current_event.arguments) - def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = False) -> List[Any]: + def _call_sync_batch_resolver( + self, + resolver: Callable, + raise_on_error: bool = False, + aggregate: bool = True, + ) -> List[Any]: """ Calls a synchronous batch resolver function for each event in the current batch. @@ -179,6 +184,10 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F raise_on_error: bool A flag indicating whether to raise an error when processing batches with failed items. Defaults to False, which means errors are handled without raising exceptions. + aggregate: bool + A flag indicating whether the batch items should be processed at once or individually. + If True (default), the batch resolver will process all items in the batch as a single event. + If False, the batch resolver will process each item in the batch individually. Returns ------- @@ -188,6 +197,17 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F logger.debug(f"Graceful error handling flag {raise_on_error=}") + # Checks whether the entire batch should be processed at once + if aggregate: + # Process the entire batch + response = resolver(event=self.current_batch_event) + + if not isinstance(response, List): + raise InvalidBatchResponse("The response must be a List when using batch resolvers") + + return response + + # Non aggregated events, so we call this event list x times # Stop on first exception we encounter if raise_on_error: return [ @@ -206,7 +226,12 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F return results - async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: bool = False) -> List[Any]: + async def _call_async_batch_resolver( + self, + resolver: Callable, + raise_on_error: bool = False, + aggregate: bool = True, + ) -> List[Any]: """ Asynchronously call a batch resolver for each event in the current batch. @@ -217,6 +242,10 @@ async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: b raise_on_error: bool A flag indicating whether to raise an error when processing batches with failed items. Defaults to False, which means errors are handled without raising exceptions. + aggregate: bool + A flag indicating whether the batch items should be processed at once or individually. + If True (default), the batch resolver will process all items in the batch as a single event. + If False, the batch resolver will process each item in the batch individually. Returns ------- @@ -225,7 +254,17 @@ async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: b """ logger.debug(f"Graceful error handling flag {raise_on_error=}") - response = [] + + response: List = [] + + # Checks whether the entire batch should be processed at once + if aggregate: + # Process the entire batch + response.extend(await asyncio.gather(resolver(event=self.current_batch_event))) + if not isinstance(response[0], List): + raise InvalidBatchResponse("The response must be a List when using batch resolvers") + + return response[0] # Prime coroutines tasks = [resolver(event=e, **e.arguments) for e in self.current_batch_event] @@ -286,7 +325,11 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv if resolver: logger.debug(f"Found sync resolver. {resolver=}, {field_name=}") - return self._call_sync_batch_resolver(resolver=resolver["func"], raise_on_error=resolver["raise_on_error"]) + return self._call_sync_batch_resolver( + resolver=resolver["func"], + raise_on_error=resolver["raise_on_error"], + aggregate=resolver["aggregate"], + ) if async_resolver: logger.debug(f"Found async resolver. {resolver=}, {field_name=}") @@ -294,6 +337,7 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv self._call_async_batch_resolver( resolver=async_resolver["func"], raise_on_error=async_resolver["raise_on_error"], + aggregate=async_resolver["aggregate"], ), ) @@ -371,6 +415,7 @@ def batch_resolver( type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False, + aggregate: bool = True, ) -> Callable: """Registers batch resolver function for GraphQL type and field name. @@ -385,6 +430,10 @@ def batch_resolver( GraphQL field e.g., getTodo, createTodo, by default None raise_on_error : bool, optional Whether to fail entire batch upon error, or handle errors gracefully (None), by default False + aggregate: bool + A flag indicating whether the batch items should be processed at once or individually. + If True (default), the batch resolver will process all items in the batch as a single event. + If False, the batch resolver will process each item in the batch individually. Returns ------- @@ -395,6 +444,7 @@ def batch_resolver( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, + aggregate=aggregate, ) def async_batch_resolver( @@ -402,9 +452,11 @@ def async_batch_resolver( type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False, + aggregate: bool = True, ) -> Callable: return self._async_batch_resolver_registry.register( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, + aggregate=aggregate, ) diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py index 4a97325f7da..fe86f502d65 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py @@ -13,6 +13,7 @@ def register( type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False, + aggregate: bool = True, ) -> Callable: """Registers the resolver for field_name @@ -25,6 +26,10 @@ def register( raise_on_error: bool A flag indicating whether to raise an error when processing batches with failed items. Defaults to False, which means errors are handled without raising exceptions. + aggregate: bool + A flag indicating whether the batch items should be processed at once or individually. + If True (default), the batch resolver will process all items in the batch as a single event. + If False, the batch resolver will process each item in the batch individually. Return ---------- @@ -34,7 +39,11 @@ def register( def _register(func) -> Callable: logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") - self.resolvers[f"{type_name}.{field_name}"] = {"func": func, "raise_on_error": raise_on_error} + self.resolvers[f"{type_name}.{field_name}"] = { + "func": func, + "raise_on_error": raise_on_error, + "aggregate": aggregate, + } return func return _register diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/base.py b/aws_lambda_powertools/event_handler/graphql_appsync/base.py index c95d300e5cf..ad32d0d17f8 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/base.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/base.py @@ -49,6 +49,7 @@ def batch_resolver( type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False, + aggregate: bool = True, ) -> Callable: """ Retrieve a batch resolver function for a specific type and field. @@ -62,6 +63,10 @@ def batch_resolver( raise_on_error: bool A flag indicating whether to raise an error when processing batches with failed items. Defaults to False, which means errors are handled without raising exceptions. + aggregate: bool + A flag indicating whether the batch items should be processed at once or individually. + If True (default), the batch resolver will process all items in the batch as a single event. + If False, the batch resolver will process each item in the batch individually. Examples -------- @@ -95,6 +100,7 @@ def async_batch_resolver( type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False, + aggregate: bool = True, ) -> Callable: """ Retrieve a batch resolver function for a specific type and field and runs async. @@ -108,6 +114,10 @@ def async_batch_resolver( raise_on_error: bool A flag indicating whether to raise an error when processing batches with failed items. Defaults to False, which means errors are handled without raising exceptions. + aggregate: bool + A flag indicating whether the batch items should be processed at once or individually. + If True (default), the batch resolver will process all items in the batch as a single event. + If False, the batch resolver will process each item in the batch individually. Examples -------- diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py b/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py index 2be78bca69d..f98a75b6f17 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py @@ -2,3 +2,9 @@ class ResolverNotFoundError(Exception): """ When a resolver is not found during a lookup. """ + + +class InvalidBatchResponse(Exception): + """ + When a batch response something different from a List + """ diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py index 44d701ca4ab..2046f09e03c 100644 --- a/aws_lambda_powertools/event_handler/graphql_appsync/router.py +++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py @@ -21,11 +21,13 @@ def batch_resolver( type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False, + aggregate: bool = True, ) -> Callable: return self._batch_resolver_registry.register( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, + aggregate=aggregate, ) def async_batch_resolver( @@ -33,11 +35,13 @@ def async_batch_resolver( type_name: str = "*", field_name: Optional[str] = None, raise_on_error: bool = False, + aggregate: bool = True, ) -> Callable: return self._async_batch_resolver_registry.register( field_name=field_name, type_name=type_name, raise_on_error=raise_on_error, + aggregate=aggregate, ) def append_context(self, **additional_context): diff --git a/tests/e2e/event_handler_appsync/files/schema.graphql b/tests/e2e/event_handler_appsync/files/schema.graphql index abc2f93562b..9733ba2f666 100644 --- a/tests/e2e/event_handler_appsync/files/schema.graphql +++ b/tests/e2e/event_handler_appsync/files/schema.graphql @@ -17,4 +17,6 @@ type Post { downs: Int relatedPosts: [Post] relatedPostsAsync: [Post] -} \ No newline at end of file + relatedPostsAggregate: [Post] + relatedPostsAsyncAggregate: [Post] +} diff --git a/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py b/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py index e08b0b93a29..594290f478d 100644 --- a/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py +++ b/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py @@ -76,6 +76,7 @@ class Post(BaseModel): downs: str +# PROCESSING SINGLE RESOLVERS @app.resolver(type_name="Query", field_name="getPost") def get_post(post_id: str = "") -> dict: post = Post(**posts[post_id]).dict() @@ -87,15 +88,27 @@ def all_posts() -> List[dict]: return list(posts.values()) -@app.batch_resolver(type_name="Post", field_name="relatedPosts") +# PROCESSING BATCH WITHOUT AGGREGATION +@app.batch_resolver(type_name="Post", field_name="relatedPosts", aggregate=False) def related_posts(event: AppSyncResolverEvent) -> Optional[list]: return posts_related[event.source["post_id"]] if event.source else None -@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsync") +@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsync", aggregate=False) async def related_posts_async(event: AppSyncResolverEvent) -> Optional[list]: return posts_related[event.source["post_id"]] if event.source else None +# PROCESSING BATCH WITH AGGREGATION +@app.batch_resolver(type_name="Post", field_name="relatedPostsAggregate") +def related_posts_aggregate(event: List[AppSyncResolverEvent]) -> Optional[list]: + return [posts_related[record.source.get("post_id")] for record in event] + + +@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsyncAggregate") +async def related_posts_async_aggregate(event: List[AppSyncResolverEvent]) -> Optional[list]: + return [posts_related[record.source.get("post_id")] for record in event] + + def lambda_handler(event, context: LambdaContext) -> dict: return app.resolve(event, context) diff --git a/tests/e2e/event_handler_appsync/infrastructure.py b/tests/e2e/event_handler_appsync/infrastructure.py index 155d9f7c2fc..1a07270572a 100644 --- a/tests/e2e/event_handler_appsync/infrastructure.py +++ b/tests/e2e/event_handler_appsync/infrastructure.py @@ -58,5 +58,19 @@ def _create_appsync_endpoint(self, function: Function): max_batch_size=10, ) + lambda_datasource.create_resolver( + "QueryGetPostRelatedResolverAggregate", + type_name="Post", + field_name="relatedPostsAggregate", + max_batch_size=10, + ) + + lambda_datasource.create_resolver( + "QueryGetPostRelatedAsyncResolverAggregate", + type_name="Post", + field_name="relatedPostsAsyncAggregate", + max_batch_size=10, + ) + CfnOutput(self.stack, "GraphQLHTTPUrl", value=api.graphql_url) CfnOutput(self.stack, "GraphQLAPIKey", value=api.api_key) diff --git a/tests/e2e/event_handler_appsync/test_appsync_resolvers.py b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py index 469c6d4946a..e8574ac233c 100644 --- a/tests/e2e/event_handler_appsync/test_appsync_resolvers.py +++ b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py @@ -75,7 +75,7 @@ def test_appsync_get_post(appsync_endpoint, appsync_access_key): @pytest.mark.xdist_group(name="event_handler") -def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): +def test_appsync_get_related_posts_batch_without_aggregate(appsync_endpoint, appsync_access_key): # GIVEN post_id = "2" related_posts_ids = ["3", "5"] @@ -110,3 +110,41 @@ def test_appsync_get_related_posts_batch(appsync_endpoint, appsync_access_key): assert post["post_id"] in related_posts_ids for post in data["getPost"]["relatedPostsAsync"]: assert post["post_id"] in related_posts_ids + + +@pytest.mark.xdist_group(name="event_handler") +def test_appsync_get_related_posts_batch_with_aggregate(appsync_endpoint, appsync_access_key): + # GIVEN + post_id = "2" + related_posts_ids = ["3", "5"] + + body = { + "query": f'query MyQuery {{ getPost(post_id: "{post_id}") \ + {{ post_id relatedPostsAggregate {{ post_id }} relatedPostsAsyncAggregate {{ post_id }} }} }}', + "variables": None, + "operationName": "MyQuery", + } + + # WHEN + response = data_fetcher.get_http_response( + Request( + method="POST", + url=appsync_endpoint, + json=body, + headers={"x-api-key": appsync_access_key, "Content-Type": "application/json"}, + ), + ) + + # THEN expect a HTTP 200 response and content return Post id with dependent Posts id's + assert response.status_code == 200 + assert response.content is not None + + data = json.loads(response.content.decode("ascii"))["data"] + + assert data["getPost"]["post_id"] == post_id + assert len(data["getPost"]["relatedPostsAggregate"]) == len(related_posts_ids) + assert len(data["getPost"]["relatedPostsAsyncAggregate"]) == len(related_posts_ids) + for post in data["getPost"]["relatedPostsAggregate"]: + assert post["post_id"] in related_posts_ids + for post in data["getPost"]["relatedPostsAsyncAggregate"]: + assert post["post_id"] in related_posts_ids diff --git a/tests/functional/event_handler/required_dependencies/appsync/__init__.py b/tests/functional/event_handler/required_dependencies/appsync/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/functional/event_handler/required_dependencies/test_appsync.py b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py similarity index 70% rename from tests/functional/event_handler/required_dependencies/test_appsync.py rename to tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py index dae9b0e2a97..f8a1366825e 100644 --- a/tests/functional/event_handler/required_dependencies/test_appsync.py +++ b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py @@ -1,310 +1,17 @@ -import asyncio -from typing import Optional +from typing import List, Optional import pytest from aws_lambda_powertools.event_handler import AppSyncResolver -from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import ResolverNotFoundError +from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import InvalidBatchResponse, ResolverNotFoundError from aws_lambda_powertools.event_handler.graphql_appsync.router import Router from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.warnings import PowertoolsUserWarning -from tests.functional.utils import load_event -def test_direct_resolver(): - # Check whether we can handle an example appsync direct resolver - mock_event = load_event("appSyncDirectResolver.json") - - app = AppSyncResolver() - - @app.resolver(field_name="createSomething") - def create_something(id: str): # noqa AA03 VNE003 - assert app.lambda_context == {} - return id - - # Call the implicit handler - result = app(mock_event, {}) - - assert result == "my identifier" - - -def test_amplify_resolver(): - # Check whether we can handle an example appsync resolver - mock_event = load_event("appSyncResolverEvent.json") - - app = AppSyncResolver() - - @app.resolver(type_name="Merchant", field_name="locations") - def get_location(page: int, size: int, name: str): - assert app.current_event is not None - assert isinstance(app.current_event, AppSyncResolverEvent) - assert page == 2 - assert size == 1 - return name - - def handler(event, context): - # Call the explicit resolve function - return app.resolve(event, context) - - result = handler(mock_event, {}) - assert result == "value" - - -def test_resolver_no_params(): - # GIVEN - app = AppSyncResolver() - - @app.resolver(type_name="Query", field_name="noParams") - def no_params(): - return "no_params has no params" - - event = {"typeName": "Query", "fieldName": "noParams", "arguments": {}} - - # WHEN - result = app.resolve(event, LambdaContext()) - - # THEN - assert result == "no_params has no params" - - -def test_resolver_value_error(): - # GIVEN no defined field resolver - app = AppSyncResolver() - - # WHEN - with pytest.raises(ValueError) as exp: - event = {"typeName": "type", "fieldName": "field", "arguments": {}} - app.resolve(event, LambdaContext()) - - # THEN - assert exp.value.args[0] == "No resolver found for 'type.field'" - - -def test_resolver_yield(): - # GIVEN - app = AppSyncResolver() - - mock_event = {"typeName": "Customer", "fieldName": "field", "arguments": {}} - - @app.resolver(field_name="field") - def func_yield(): - yield "value" - - # WHEN - mock_context = LambdaContext() - result = app.resolve(mock_event, mock_context) - - # THEN - assert next(result) == "value" - - -def test_resolver_multiple_mappings(): - # GIVEN - app = AppSyncResolver() - - @app.resolver(field_name="listLocations") - @app.resolver(field_name="locations") - def get_locations(name: str, description: str = ""): - return name + description - - # WHEN - mock_event1 = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} - mock_event2 = { - "typeName": "Merchant", - "fieldName": "locations", - "arguments": {"name": "value2", "description": "description"}, - } - result1 = app.resolve(mock_event1, LambdaContext()) - result2 = app.resolve(mock_event2, LambdaContext()) - - # THEN - assert result1 == "value" - assert result2 == "value2description" - - -def test_resolver_async(): - # GIVEN - app = AppSyncResolver() - - mock_event = {"typeName": "Customer", "fieldName": "field", "arguments": {}} - - @app.resolver(field_name="field") - async def get_async(): - await asyncio.sleep(0.0001) - return "value" - - # WHEN - mock_context = LambdaContext() - result = app.resolve(mock_event, mock_context) - - # THEN - assert asyncio.run(result) == "value" - - -def test_resolve_custom_data_model(): - # Check whether we can handle an example appsync direct resolver - mock_event = load_event("appSyncDirectResolver.json") - - class MyCustomModel(AppSyncResolverEvent): - @property - def country_viewer(self): - return self.request_headers.get("cloudfront-viewer-country") - - app = AppSyncResolver() - - @app.resolver(field_name="createSomething") - def create_something(id: str): # noqa AA03 VNE003 - return id - - # Call the implicit handler - result = app(event=mock_event, context=LambdaContext(), data_model=MyCustomModel) - - assert result == "my identifier" - - assert app.current_event.country_viewer == "US" - - -def test_resolver_include_resolver(): - # GIVEN - app = AppSyncResolver() - router = Router() - - @router.resolver(type_name="Query", field_name="listLocations") - def get_locations(name: str): - return f"get_locations#{name}" - - @app.resolver(field_name="listLocations2") - def get_locations2(name: str): - return f"get_locations2#{name}" - - app.include_router(router) - - # WHEN - mock_event1 = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} - mock_event2 = {"typeName": "Query", "fieldName": "listLocations2", "arguments": {"name": "value"}} - result1 = app.resolve(mock_event1, LambdaContext()) - result2 = app.resolve(mock_event2, LambdaContext()) - - # THEN - assert result1 == "get_locations#value" - assert result2 == "get_locations2#value" - - -def test_resolver_include_mixed_resolver(): - # GIVEN - app = AppSyncResolver() - router = Router() - - @router.batch_resolver(type_name="Query", field_name="listLocations") - def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return f"get_locations#{name}#" + event.source["id"] - - @app.resolver(field_name="listLocations2") - def get_locations2(name: str) -> str: - return f"get_locations2#{name}" - - app.include_router(router) - - # WHEN - mock_event1 = [ - { - "typeName": "Query", - "info": { - "fieldName": "listLocations", - "parentTypeName": "Query", - }, - "fieldName": "listLocations", - "arguments": {"name": "value"}, - "source": { - "id": "1", - }, - }, - ] - mock_event2 = { - "typeName": "Query", - "info": { - "fieldName": "listLocations2", - "parentTypeName": "Post", - }, - "fieldName": "listLocations2", - "arguments": {"name": "value"}, - } - - result1 = app.resolve(mock_event1, LambdaContext()) - result2 = app.resolve(mock_event2, LambdaContext()) - - # THEN - assert result1 == ["get_locations#value#1"] - assert result2 == "get_locations2#value" - - -def test_append_context(): - app = AppSyncResolver() - app.append_context(is_admin=True) - assert app.context.get("is_admin") is True - - -def test_router_append_context(): - router = Router() - router.append_context(is_admin=True) - assert router.context.get("is_admin") is True - - -def test_route_context_is_cleared_after_resolve(): - # GIVEN - app = AppSyncResolver() - event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} - - @app.resolver(field_name="listLocations") - def get_locations(name: str): - return f"get_locations#{name}" - - # WHEN event resolution kicks in - app.append_context(is_admin=True) - app.resolve(event, {}) - - # THEN context should be empty - assert app.context == {} - - -def test_router_has_access_to_app_context(): - # GIVEN - app = AppSyncResolver() - router = Router() - event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} - - @router.resolver(type_name="Query", field_name="listLocations") - def get_locations(name: str): - if router.context.get("is_admin"): - return f"get_locations#{name}" - - app.include_router(router) - - # WHEN - app.append_context(is_admin=True) - ret = app.resolve(event, {}) - - # THEN - assert ret == "get_locations#value" - assert router.context == {} - - -def test_include_router_merges_context(): - # GIVEN - app = AppSyncResolver() - router = Router() - - # WHEN - app.append_context(is_admin=True) - router.append_context(product_access=True) - - app.include_router(router) - - assert app.context == router.context - - -def test_resolve_batch_processing_with_related_events(): +# TESTS RECEIVING THE EVENT PARTIALLY AND PROCESS EACH RECORD PER TIME. +def test_resolve_batch_processing_with_related_events_one_at_time(): # GIVEN An event with multiple requests to fetch related posts for different post IDs. event = [ { @@ -386,7 +93,7 @@ def test_resolve_batch_processing_with_related_events(): app = AppSyncResolver() - @app.batch_resolver(type_name="Post", field_name="relatedPosts") + @app.batch_resolver(type_name="Post", field_name="relatedPosts", aggregate=False) def related_posts(event: AppSyncResolverEvent) -> Optional[list]: return posts_related[event.source["post_id"]] @@ -402,7 +109,7 @@ def related_posts(event: AppSyncResolverEvent) -> Optional[list]: # Batch resolver tests -def test_resolve_batch_processing_with_simple_queries(): +def test_resolve_batch_processing_with_simple_queries_one_at_time(): # GIVEN a list of events representing GraphQL queries for listing locations event = [ { @@ -446,7 +153,7 @@ def test_resolve_batch_processing_with_simple_queries(): app = AppSyncResolver() # WHEN the batch resolver for the listLocations field is defined - @app.batch_resolver(field_name="listLocations") + @app.batch_resolver(field_name="listLocations", aggregate=False) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 return event.source["id"] if event.source else None @@ -458,7 +165,7 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0 assert not app.current_event -def test_resolve_batch_processing_with_raise_on_exception(): +def test_resolve_batch_processing_with_raise_on_exception_one_at_time(): # GIVEN a list of events representing GraphQL queries for listing locations event = [ { @@ -502,7 +209,7 @@ def test_resolve_batch_processing_with_raise_on_exception(): app = AppSyncResolver() # WHEN the sync batch resolver for the 'listLocations' field is defined with raise_on_error=True - @app.batch_resolver(field_name="listLocations", raise_on_error=True) + @app.batch_resolver(field_name="listLocations", raise_on_error=True, aggregate=False) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -511,7 +218,7 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0 app.resolve(event, LambdaContext()) -def test_async_resolve_batch_processing_with_raise_on_exception(): +def test_async_resolve_batch_processing_with_raise_on_exception_one_at_time(): # GIVEN a list of events representing GraphQL queries for listing locations event = [ { @@ -555,7 +262,7 @@ def test_async_resolve_batch_processing_with_raise_on_exception(): app = AppSyncResolver() # WHEN the async batch resolver for the 'listLocations' field is defined with raise_on_error=True - @app.async_batch_resolver(field_name="listLocations", raise_on_error=True) + @app.async_batch_resolver(field_name="listLocations", raise_on_error=True, aggregate=False) async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -564,7 +271,7 @@ async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # no app.resolve(event, LambdaContext()) -def test_resolve_batch_processing_without_exception(): +def test_resolve_batch_processing_without_exception_one_at_time(): event = [ { "typeName": "Query", @@ -606,7 +313,7 @@ def test_resolve_batch_processing_without_exception(): app = AppSyncResolver() - @app.batch_resolver(field_name="listLocations", raise_on_error=False) + @app.batch_resolver(field_name="listLocations", raise_on_error=False, aggregate=False) def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -618,7 +325,7 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0 assert not app.current_event -def test_resolve_async_batch_processing_without_exception(): +def test_resolve_async_batch_processing_without_exception_one_at_time(): # GIVEN a list of events representing GraphQL queries for listing locations event = [ { @@ -662,7 +369,7 @@ def test_resolve_async_batch_processing_without_exception(): app = AppSyncResolver() # WHEN the batch resolver for the 'listLocations' field is defined with raise_on_error=False - @app.async_batch_resolver(field_name="listLocations", raise_on_error=False) + @app.async_batch_resolver(field_name="listLocations", raise_on_error=False, aggregate=False) async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003 raise RuntimeError @@ -673,7 +380,7 @@ async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # no assert result == [None, None, None] -def test_resolver_batch_with_resolver_not_found(): +def test_resolver_batch_with_resolver_not_found_one_at_time(): # GIVEN a AppSyncResolver app = AppSyncResolver() router = Router() @@ -695,9 +402,9 @@ def test_resolver_batch_with_resolver_not_found(): }, ] - @router.batch_resolver(type_name="Query", field_name="listLocations") + @router.batch_resolver(type_name="Query", field_name="listLocations", aggregate=False) def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] + return f"get_locations#{name}#" + event.source["id"] app.include_router(router) @@ -728,13 +435,13 @@ def test_resolver_batch_with_sync_and_async_resolver_at_same_time(): }, ] - @router.batch_resolver(type_name="Query", field_name="listCars") + @router.batch_resolver(type_name="Query", field_name="listCars", aggregate=False) def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] + return f"get_locations#{name}#" + event.source["id"] - @router.async_batch_resolver(type_name="Query", field_name="listCars") + @router.async_batch_resolver(type_name="Query", field_name="listCars", aggregate=False) async def get_locations_async(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] + return f"get_locations#{name}#" + event.source["id"] app.include_router(router) @@ -748,11 +455,11 @@ def test_batch_resolver_with_router(): app = AppSyncResolver() router = Router() - @router.batch_resolver(type_name="Query", field_name="listLocations") + @router.batch_resolver(type_name="Query", field_name="listLocations", aggregate=False) def get_locations(event: AppSyncResolverEvent, name: str) -> str: return f"get_locations#{name}#" + event.source["id"] - @router.batch_resolver(field_name="listLocations2") + @router.batch_resolver(field_name="listLocations2", aggregate=False) def get_locations2(event: AppSyncResolverEvent, name: str) -> str: return f"get_locations2#{name}#" + event.source["id"] @@ -839,7 +546,7 @@ def test_resolve_async_batch_processing(): app = AppSyncResolver() # WHEN the async batch resolver for the 'listLocations' field is defined - @app.async_batch_resolver(field_name="listLocations") + @app.async_batch_resolver(field_name="listLocations", aggregate=False) async def create_something(event: AppSyncResolverEvent) -> Optional[list]: return event.source["id"] if event.source else None @@ -855,13 +562,13 @@ def test_resolve_async_batch_and_sync_singular_processing(): app = AppSyncResolver() router = Router() - @router.async_batch_resolver(type_name="Query", field_name="listLocations") + @router.async_batch_resolver(type_name="Query", field_name="listLocations", aggregate=False) async def get_locations(event: AppSyncResolverEvent, name: str) -> str: - return "get_locations#" + name + "#" + event.source["id"] + return f"get_locations#{name}#" + event.source["id"] @app.resolver(type_name="Query", field_name="listLocation") def get_location(name: str) -> str: - return "get_location#" + name + return f"get_location#{name}" app.include_router(router) @@ -895,11 +602,11 @@ def test_async_resolver_include_batch_resolver(): app = AppSyncResolver() router = Router() - @router.async_batch_resolver(type_name="Query", field_name="listLocations") + @router.async_batch_resolver(type_name="Query", field_name="listLocations", aggregate=False) async def get_locations(event: AppSyncResolverEvent, name: str) -> str: return f"get_locations#{name}#" + event.source["id"] - @app.async_batch_resolver(field_name="listLocations2") + @app.async_batch_resolver(field_name="listLocations2", aggregate=False) async def get_locations2(event: AppSyncResolverEvent, name: str) -> str: return f"get_locations2#{name}#" + event.source["id"] @@ -942,3 +649,181 @@ async def get_locations2(event: AppSyncResolverEvent, name: str) -> str: # THEN Verify that the results match the expected values assert result1 == ["get_locations#value#1"] assert result2 == ["get_locations2#value#2"] + + +def test_resolve_batch_processing_with_simple_queries_with_aggregate(): + # GIVEN a list of events representing GraphQL queries for listing locations + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": [3, 4], + }, + }, + ] + + app = AppSyncResolver() + + # WHEN the batch resolver for the listLocations field is defined + @app.batch_resolver(field_name="listLocations") + def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + results = [] + for record in event: + results.append(record.source.get("id") if record.source else None) + + return results + + # THEN the resolver should correctly process the batch of queries + result = app.resolve(event, LambdaContext()) + assert result == [appsync_event["source"]["id"] for appsync_event in event] + + assert app.current_batch_event and len(app.current_batch_event) == len(event) + assert not app.current_event + + +def test_resolve_async_batch_processing_with_simple_queries_with_aggregate(): + # GIVEN a list of events representing GraphQL queries for listing locations + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "2", + }, + }, + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": [3, 4], + }, + }, + ] + + app = AppSyncResolver() + + # WHEN the batch resolver for the listLocations field is defined + @app.async_batch_resolver(field_name="listLocations") + async def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + results = [] + for record in event: + results.append(record.source.get("id") if record.source else None) + + return results + + # THEN the resolver should correctly process the batch of queries + result = app.resolve(event, LambdaContext()) + assert result == [appsync_event["source"]["id"] for appsync_event in event] + + assert app.current_batch_event and len(app.current_batch_event) == len(event) + assert not app.current_event + + +def test_resolve_batch_processing_with_aggregate_and_returning_a_non_list(): + # GIVEN a list of events representing GraphQL queries for listing locations + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + ] + + app = AppSyncResolver() + + # WHEN the batch resolver for the listLocations field is defined + @app.batch_resolver(field_name="listLocations") + def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + return event[0].source.get("id") if event[0].source else None + + # THEN the resolver should raise a RuntimeError when processing the batch of queries + with pytest.raises(InvalidBatchResponse): + app.resolve(event, LambdaContext()) + + +def test_resolve_async_batch_processing_with_aggregate_and_returning_a_non_list(): + # GIVEN a list of events representing GraphQL queries for listing locations + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + ] + + app = AppSyncResolver() + + # WHEN the batch resolver for the listLocations field is defined + @app.async_batch_resolver(field_name="listLocations") + async def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + return event[0].source.get("id") if event[0].source else None + + # THEN the resolver should raise a RuntimeError when processing the batch of queries + with pytest.raises(InvalidBatchResponse): + app.resolve(event, LambdaContext()) diff --git a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py new file mode 100644 index 00000000000..1ace266e731 --- /dev/null +++ b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py @@ -0,0 +1,253 @@ +import asyncio + +import pytest + +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.event_handler.graphql_appsync.router import Router +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext +from tests.functional.utils import load_event + + +def test_direct_resolver(): + # Check whether we can handle an example appsync direct resolver + mock_event = load_event("appSyncDirectResolver.json") + + app = AppSyncResolver() + + @app.resolver(field_name="createSomething") + def create_something(id: str): # noqa AA03 VNE003 + assert app.lambda_context == {} + return id + + # Call the implicit handler + result = app(mock_event, {}) + + assert result == "my identifier" + + +def test_amplify_resolver(): + # Check whether we can handle an example appsync resolver + mock_event = load_event("appSyncResolverEvent.json") + + app = AppSyncResolver() + + @app.resolver(type_name="Merchant", field_name="locations") + def get_location(page: int, size: int, name: str): + assert app.current_event is not None + assert isinstance(app.current_event, AppSyncResolverEvent) + assert page == 2 + assert size == 1 + return name + + def handler(event, context): + # Call the explicit resolve function + return app.resolve(event, context) + + result = handler(mock_event, {}) + assert result == "value" + + +def test_resolver_no_params(): + # GIVEN + app = AppSyncResolver() + + @app.resolver(type_name="Query", field_name="noParams") + def no_params(): + return "no_params has no params" + + event = {"typeName": "Query", "fieldName": "noParams", "arguments": {}} + + # WHEN + result = app.resolve(event, LambdaContext()) + + # THEN + assert result == "no_params has no params" + + +def test_resolver_value_error(): + # GIVEN no defined field resolver + app = AppSyncResolver() + + # WHEN + with pytest.raises(ValueError) as exp: + event = {"typeName": "type", "fieldName": "field", "arguments": {}} + app.resolve(event, LambdaContext()) + + # THEN + assert exp.value.args[0] == "No resolver found for 'type.field'" + + +def test_resolver_yield(): + # GIVEN + app = AppSyncResolver() + + mock_event = {"typeName": "Customer", "fieldName": "field", "arguments": {}} + + @app.resolver(field_name="field") + def func_yield(): + yield "value" + + # WHEN + mock_context = LambdaContext() + result = app.resolve(mock_event, mock_context) + + # THEN + assert next(result) == "value" + + +def test_resolver_multiple_mappings(): + # GIVEN + app = AppSyncResolver() + + @app.resolver(field_name="listLocations") + @app.resolver(field_name="locations") + def get_locations(name: str, description: str = ""): + return name + description + + # WHEN + mock_event1 = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} + mock_event2 = { + "typeName": "Merchant", + "fieldName": "locations", + "arguments": {"name": "value2", "description": "description"}, + } + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == "value" + assert result2 == "value2description" + + +def test_resolver_async(): + # GIVEN + app = AppSyncResolver() + + mock_event = {"typeName": "Customer", "fieldName": "field", "arguments": {}} + + @app.resolver(field_name="field") + async def get_async(): + await asyncio.sleep(0.0001) + return "value" + + # WHEN + mock_context = LambdaContext() + result = app.resolve(mock_event, mock_context) + + # THEN + assert asyncio.run(result) == "value" + + +def test_resolve_custom_data_model(): + # Check whether we can handle an example appsync direct resolver + mock_event = load_event("appSyncDirectResolver.json") + + class MyCustomModel(AppSyncResolverEvent): + @property + def country_viewer(self): + return self.request_headers.get("cloudfront-viewer-country") + + app = AppSyncResolver() + + @app.resolver(field_name="createSomething") + def create_something(id: str): # noqa AA03 VNE003 + return id + + # Call the implicit handler + result = app(event=mock_event, context=LambdaContext(), data_model=MyCustomModel) + + assert result == "my identifier" + + assert app.current_event.country_viewer == "US" + + +def test_resolver_include_resolver(): + # GIVEN + app = AppSyncResolver() + router = Router() + + @router.resolver(type_name="Query", field_name="listLocations") + def get_locations(name: str): + return f"get_locations#{name}" + + @app.resolver(field_name="listLocations2") + def get_locations2(name: str): + return f"get_locations2#{name}" + + app.include_router(router) + + # WHEN + mock_event1 = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} + mock_event2 = {"typeName": "Query", "fieldName": "listLocations2", "arguments": {"name": "value"}} + result1 = app.resolve(mock_event1, LambdaContext()) + result2 = app.resolve(mock_event2, LambdaContext()) + + # THEN + assert result1 == "get_locations#value" + assert result2 == "get_locations2#value" + + +def test_append_context(): + app = AppSyncResolver() + app.append_context(is_admin=True) + assert app.context.get("is_admin") is True + + +def test_router_append_context(): + router = Router() + router.append_context(is_admin=True) + assert router.context.get("is_admin") is True + + +def test_route_context_is_cleared_after_resolve(): + # GIVEN + app = AppSyncResolver() + event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} + + @app.resolver(field_name="listLocations") + def get_locations(name: str): + return f"get_locations#{name}" + + # WHEN event resolution kicks in + app.append_context(is_admin=True) + app.resolve(event, {}) + + # THEN context should be empty + assert app.context == {} + + +def test_router_has_access_to_app_context(): + # GIVEN + app = AppSyncResolver() + router = Router() + event = {"typeName": "Query", "fieldName": "listLocations", "arguments": {"name": "value"}} + + @router.resolver(type_name="Query", field_name="listLocations") + def get_locations(name: str): + if router.context.get("is_admin"): + return f"get_locations#{name}" + + app.include_router(router) + + # WHEN + app.append_context(is_admin=True) + ret = app.resolve(event, {}) + + # THEN + assert ret == "get_locations#value" + assert router.context == {} + + +def test_include_router_merges_context(): + # GIVEN + app = AppSyncResolver() + router = Router() + + # WHEN + app.append_context(is_admin=True) + router.append_context(product_access=True) + + app.include_router(router) + + assert app.context == router.context From 449316597f8ef8336edc4e20b5a711b70030c341 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 25 Jun 2024 00:25:22 +0100 Subject: [PATCH 57/75] Refactoring examples + docs --- docs/core/event_handler/appsync.md | 28 +++++++++++++------ ...tting_started_with_batch_async_resolver.py | 18 ++++++++++-- .../getting_started_with_batch_resolver.py | 18 ++++++++++-- ...rted_with_batch_resolver_handling_error.py | 25 +++++++++++++++++ ..._started_with_batch_resolver_individual.py | 25 +++++++++++++++++ ...g_started_with_batch_resolver_payload.json | 2 +- 6 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py create mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index ceb71f0a527..e2d1b630647 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -290,7 +290,7 @@ You can use `append_context` when you want to share data between your App and Ro ### Batch processing -We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple events in a batch and prevent multiple lambda executions, configure your Appsync to group events and use the `@batch_resolver` or `@async_batch_resolver` decorators. +We support AWS Appsync's batching mechanism for Lambda Resolvers. It prevents multiple Lambda executions by grouping events and using the `@batch_resolver` or `@async_batch_resolver` decorators to resolve the entire batch. ???+ info If you want to understand more how to configure batch processing for the AppSync, please follow this [guide](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"}. @@ -300,28 +300,38 @@ We support Appsync's batching mechanism for Lambda Resolvers. To handle multiple --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver.py" ``` + 1. The entire batch is sent to the resolver, and you need to iterate through it to process all records. + === "getting_started_with_batch_resolver_payload.json" ```json hl_lines="4 16 21 29 41 46" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` -#### Handling exceptions +#### Processing Batch items individually -By default, records that fail during Lambda execution return `None` to ensure the entire batch doesn't fail due to processing errors. However, you have the option to actively return exceptions for debugging or troubleshooting. +You can process each item in the batch individually, and we can handle exceptions for you. However, it's important to note that utilizing this method may increase the execution time of your Lambda function. ???+ tip For better error handling, you may need to configure response mapping templates and specify error keys. Explore more on returning individual errors [here](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#advanced-use-case-batching){target="_blank"}. -=== "enable_exceptions_batch_resolver.py" - ```python hl_lines="3 7 21" - --8<-- "examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py" +=== "getting_started_with_batch_resolver_individual.py" + ```python hl_lines="3 7 17" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py" + ``` + + 1. You need to disable the aggregated event by using `aggregate` flag. + The resolver receives and processes each record one at a time. + +=== "getting_started_with_batch_resolver_handling_error.py" + ```python hl_lines="3 7 17" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py" ``` - 1. You can enable the exceptions by setting `raise_on_error` to True. + 1. You can enable enable the error handling by using `raise_on_error` flag. -=== "enable_exceptions_batch_resolver_payload.json" +=== "getting_started_with_batch_resolver_payload.json" ```json hl_lines="4 16 21 29 41 46" - --8<-- "examples/event_handler_graphql/src/enable_exceptions_batch_resolver_payload.json" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` #### Async diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py index c161073b5c8..ef505be7776 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py @@ -1,9 +1,11 @@ -from typing import Dict, Optional +from typing import Any, List, Optional +from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext +logger = Logger() app = AppSyncResolver() @@ -15,8 +17,18 @@ @app.async_batch_resolver(type_name="Query", field_name="relatedPosts") -async def related_posts(event: AppSyncResolverEvent, post_id: str) -> Optional[Dict]: - return posts_related.get(post_id, None) +async def related_posts(event: List[AppSyncResolverEvent]) -> Optional[List[Any]]: + results = [] + + for record in event: # (1)! + post_id = record.arguments.get("post_id") + try: + results.append(posts_related[post_id] if post_id else None) + # Add other logic here + except Exception: + logger.error("Error processing record", post_id=post_id) + + return results def lambda_handler(event, context: LambdaContext) -> dict: diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py index b3dd275cc88..6ef8005d72d 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py @@ -1,9 +1,11 @@ -from typing import Dict, Optional +from typing import Any, List +from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext +logger = Logger() app = AppSyncResolver() @@ -15,8 +17,18 @@ @app.batch_resolver(type_name="Query", field_name="relatedPosts") -def related_posts(event: AppSyncResolverEvent, post_id: str) -> Optional[Dict]: - return posts_related.get(post_id, None) +def related_posts(event: List[AppSyncResolverEvent]) -> List[Any]: + results = [] + + for record in event: # (1)! + post_id = record.arguments.get("post_id") + try: + results.append(posts_related[post_id] if post_id else None) + # Add other logic here + except Exception: + logger.error("Error processing record", post_id=post_id) + + return results def lambda_handler(event, context: LambdaContext) -> dict: diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py new file mode 100644 index 00000000000..a4862c6e55b --- /dev/null +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py @@ -0,0 +1,25 @@ +from typing import Any, Dict + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() +app = AppSyncResolver() + + +posts_related = { + "1": {"title": "post1"}, + "2": {"title": "post2"}, + "3": {"title": "post3"}, +} + + +@app.batch_resolver(type_name="Query", field_name="relatedPosts", aggregate=False, raise_on_error=True) # (1)! +def related_posts(event: AppSyncResolverEvent, post_id: str = "") -> Dict[str, Any]: + return posts_related[post_id] + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py new file mode 100644 index 00000000000..731ec11813f --- /dev/null +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py @@ -0,0 +1,25 @@ +from typing import Any, Dict + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler import AppSyncResolver +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() +app = AppSyncResolver() + + +posts_related = { + "1": {"title": "post1"}, + "2": {"title": "post2"}, + "3": {"title": "post3"}, +} + + +@app.batch_resolver(type_name="Query", field_name="relatedPosts", aggregate=False) # (1)! +def related_posts(event: AppSyncResolverEvent, post_id: str = "") -> Dict[str, Any]: + return posts_related[post_id] + + +def lambda_handler(event, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json index 06bbb2daaae..f0a3c147d86 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json @@ -26,7 +26,7 @@ }, { "arguments":{ - "post_id":"10" + "post_id":"2" }, "identity":"None", "source":"None", From a778dcd78ba1486b70c898d08b87e8a574728032 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 25 Jun 2024 13:07:16 +0100 Subject: [PATCH 58/75] Addressing Heitor's feedback --- .../event_handler/appsync.py | 10 +- .../test_appsync_resolvers.py | 46 ++++++-- .../appsync/test_appsync_batch_resolvers.py | 100 ++++++++++++++++-- 3 files changed, 131 insertions(+), 25 deletions(-) diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index ab2a808189b..fb53b71c77d 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -255,16 +255,16 @@ async def _call_async_batch_resolver( logger.debug(f"Graceful error handling flag {raise_on_error=}") - response: List = [] - # Checks whether the entire batch should be processed at once if aggregate: # Process the entire batch - response.extend(await asyncio.gather(resolver(event=self.current_batch_event))) - if not isinstance(response[0], List): + ret = await resolver(event=self.current_batch_event) + if not isinstance(ret, List): raise InvalidBatchResponse("The response must be a List when using batch resolvers") - return response[0] + return ret + + response: List = [] # Prime coroutines tasks = [resolver(event=e, **e.arguments) for e in self.current_batch_event] diff --git a/tests/e2e/event_handler_appsync/test_appsync_resolvers.py b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py index e8574ac233c..35549a1fdef 100644 --- a/tests/e2e/event_handler_appsync/test_appsync_resolvers.py +++ b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py @@ -76,18 +76,29 @@ def test_appsync_get_post(appsync_endpoint, appsync_access_key): @pytest.mark.xdist_group(name="event_handler") def test_appsync_get_related_posts_batch_without_aggregate(appsync_endpoint, appsync_access_key): - # GIVEN + # GIVEN a batch event post_id = "2" related_posts_ids = ["3", "5"] body = { - "query": f'query MyQuery {{ getPost(post_id: "{post_id}") \ - {{ post_id relatedPosts {{ post_id }} relatedPostsAsync {{ post_id }} }} }}', + "query": f""" + query MyQuery {{ + getPost(post_id: "{post_id}") {{ + post_id + relatedPosts {{ + post_id + }} + relatedPostsAsync {{ + post_id + }} + }} + }} + """, "variables": None, "operationName": "MyQuery", } - # WHEN + # WHEN we invoke the AppSync API with a batch event response = data_fetcher.get_http_response( Request( method="POST", @@ -104,28 +115,41 @@ def test_appsync_get_related_posts_batch_without_aggregate(appsync_endpoint, app data = json.loads(response.content.decode("ascii"))["data"] assert data["getPost"]["post_id"] == post_id + assert len(data["getPost"]["relatedPosts"]) == len(related_posts_ids) - assert len(data["getPost"]["relatedPostsAsync"]) == len(related_posts_ids) for post in data["getPost"]["relatedPosts"]: assert post["post_id"] in related_posts_ids + + assert len(data["getPost"]["relatedPostsAsync"]) == len(related_posts_ids) for post in data["getPost"]["relatedPostsAsync"]: assert post["post_id"] in related_posts_ids @pytest.mark.xdist_group(name="event_handler") def test_appsync_get_related_posts_batch_with_aggregate(appsync_endpoint, appsync_access_key): - # GIVEN + # GIVEN a batch event post_id = "2" related_posts_ids = ["3", "5"] body = { - "query": f'query MyQuery {{ getPost(post_id: "{post_id}") \ - {{ post_id relatedPostsAggregate {{ post_id }} relatedPostsAsyncAggregate {{ post_id }} }} }}', + "query": f""" + query MyQuery {{ + getPost(post_id: "{post_id}") {{ + post_id + relatedPostsAggregate {{ + post_id + }} + relatedPostsAsyncAggregate {{ + post_id + }} + }} + }} + """, "variables": None, "operationName": "MyQuery", } - # WHEN + # WHEN we invoke the AppSync API with a batch event response = data_fetcher.get_http_response( Request( method="POST", @@ -142,9 +166,11 @@ def test_appsync_get_related_posts_batch_with_aggregate(appsync_endpoint, appsyn data = json.loads(response.content.decode("ascii"))["data"] assert data["getPost"]["post_id"] == post_id + assert len(data["getPost"]["relatedPostsAggregate"]) == len(related_posts_ids) - assert len(data["getPost"]["relatedPostsAsyncAggregate"]) == len(related_posts_ids) for post in data["getPost"]["relatedPostsAggregate"]: assert post["post_id"] in related_posts_ids + + assert len(data["getPost"]["relatedPostsAsyncAggregate"]) == len(related_posts_ids) for post in data["getPost"]["relatedPostsAsyncAggregate"]: assert post["post_id"] in related_posts_ids diff --git a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py index f8a1366825e..cc78cbf0f9c 100644 --- a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py +++ b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py @@ -694,9 +694,11 @@ def test_resolve_batch_processing_with_simple_queries_with_aggregate(): app = AppSyncResolver() - # WHEN the batch resolver for the listLocations field is defined + # WHEN the sync batch resolver for the listLocations field is defined + # WHEN using an aggregated event + # WHEN function returns a List @app.batch_resolver(field_name="listLocations") - def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + def create_something(event: List[AppSyncResolverEvent]) -> List: # noqa AA03 VNE003 results = [] for record in event: results.append(record.source.get("id") if record.source else None) @@ -754,9 +756,11 @@ def test_resolve_async_batch_processing_with_simple_queries_with_aggregate(): app = AppSyncResolver() - # WHEN the batch resolver for the listLocations field is defined + # WHEN the async batch resolver for the listLocations field is defined + # WHEN using an aggregated event + # WHEN function returns a List @app.async_batch_resolver(field_name="listLocations") - async def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + async def create_something(event: List[AppSyncResolverEvent]) -> List: # noqa AA03 VNE003 results = [] for record in event: results.append(record.source.get("id") if record.source else None) @@ -790,12 +794,14 @@ def test_resolve_batch_processing_with_aggregate_and_returning_a_non_list(): app = AppSyncResolver() - # WHEN the batch resolver for the listLocations field is defined + # WHEN the sync batch resolver for the listLocations field is defined + # WHEN using an aggregated event + # WHEN function return something different than a List @app.batch_resolver(field_name="listLocations") - def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003 return event[0].source.get("id") if event[0].source else None - # THEN the resolver should raise a RuntimeError when processing the batch of queries + # THEN the resolver should raise a InvalidBatchResponse when processing the batch of queries with pytest.raises(InvalidBatchResponse): app.resolve(event, LambdaContext()) @@ -819,11 +825,85 @@ def test_resolve_async_batch_processing_with_aggregate_and_returning_a_non_list( app = AppSyncResolver() - # WHEN the batch resolver for the listLocations field is defined + # WHEN the async batch resolver for the listLocations field is defined + # WHEN using an aggregated event + # WHEN function return something different than a List @app.async_batch_resolver(field_name="listLocations") - async def create_something(event: List[AppSyncResolverEvent]) -> Optional[list]: # noqa AA03 VNE003 + async def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003 return event[0].source.get("id") if event[0].source else None - # THEN the resolver should raise a RuntimeError when processing the batch of queries + # THEN the resolver should raise a InvalidBatchResponse when processing the batch of queries + with pytest.raises(InvalidBatchResponse): + app.resolve(event, LambdaContext()) + + +def test_resolve_sync_batch_processing_with_aggregate_and_without_return(): + # GIVEN a list of events representing GraphQL queries for listing locations + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + ] + + app = AppSyncResolver() + + # WHEN the sync batch resolver for the listLocations field is defined + # WHEN using an aggregated event + # WHEN function there is no return statement + @app.batch_resolver(field_name="listLocations") + def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003 + def do_something_with_post_id(post_id): ... + + post_id = event[0].source.get("id") if event[0].source else None + do_something_with_post_id(post_id) + + # No Return statement + + # THEN the resolver should raise a InvalidBatchResponse when processing the batch of queries + with pytest.raises(InvalidBatchResponse): + app.resolve(event, LambdaContext()) + + +def test_resolve_async_batch_processing_with_aggregate_and_without_return(): + # GIVEN a list of events representing GraphQL queries for listing locations + event = [ + { + "typeName": "Query", + "info": { + "fieldName": "listLocations", + "parentTypeName": "Post", + }, + "fieldName": "listLocations", + "arguments": {}, + "source": { + "id": "1", + }, + }, + ] + + app = AppSyncResolver() + + # WHEN the async batch resolver for the listLocations field is defined + # WHEN using an aggregated event + # WHEN function there is no return statement + @app.async_batch_resolver(field_name="listLocations") + async def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003 + def do_something_with_post_id(post_id): ... + + post_id = event[0].source.get("id") if event[0].source else None + do_something_with_post_id(post_id) + + # No Return statement + + # THEN the resolver should raise a InvalidBatchResponse when processing the batch of queries with pytest.raises(InvalidBatchResponse): app.resolve(event, LambdaContext()) From b0e1e3e4f20980968282ff882bc4406da9df2540 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 11:42:53 +0200 Subject: [PATCH 59/75] docs: add diagram to visualize n+1 problem --- docs/core/event_handler/appsync.md | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index e2d1b630647..eaffa6831b6 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -290,6 +290,47 @@ You can use `append_context` when you want to share data between your App and Ro ### Batch processing +```mermaid +stateDiagram-v2 + direction LR + LambdaInit: Lambda invocation + EventHandler: Event Handler + EventHandlerResolver: Route event based on GraphQL type/field keys + Client: Client query (getPosts) + YourLogic: Run your registered resolver function + EventHandlerResolverBuilder: Verifies response is a list + AppSyncBatchPostsResolution: query getPosts + AppSyncBatchPostsItems: get all posts data (id, title, relatedPosts) + AppSyncBatchRelatedPosts: get related posts (id, title, relatedPosts) + AppSyncBatchAggregate: aggregate batch resolver event + AppSyncBatchLimit: reached batch size limit + LambdaResponse: Lambda response + + Client --> AppSyncBatchResolverMode + state AppSyncBatchResolverMode { + [*] --> AppSyncBatchPostsResolution + AppSyncBatchPostsResolution --> AppSyncBatchPostsItems + AppSyncBatchPostsItems --> AppSyncBatchRelatedPosts: N additional queries + AppSyncBatchRelatedPosts --> AppSyncBatchRelatedPosts + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchAggregate --> AppSyncBatchLimit + } + + AppSyncBatchResolverMode --> LambdaInit: Invoke with batch results + LambdaInit --> EventHandler + + state EventHandler { + [*] --> EventHandlerResolver: app.resolve(event, context) + EventHandlerResolver --> YourLogic + YourLogic --> EventHandlerResolverBuilder + EventHandlerResolverBuilder --> LambdaResponse + } +``` + +
Batch resolvers: visualizing N+1 in `relatedPosts` field.
+ We support AWS Appsync's batching mechanism for Lambda Resolvers. It prevents multiple Lambda executions by grouping events and using the `@batch_resolver` or `@async_batch_resolver` decorators to resolve the entire batch. ???+ info From fb631eabc4cb97402abd16a03157398d2bbab829 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 11:44:23 +0200 Subject: [PATCH 60/75] docs: improve wording in lambda invoke --- docs/core/event_handler/appsync.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index eaffa6831b6..c51ca5c6f2e 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -318,7 +318,7 @@ stateDiagram-v2 AppSyncBatchAggregate --> AppSyncBatchLimit } - AppSyncBatchResolverMode --> LambdaInit: Invoke with batch results + AppSyncBatchResolverMode --> LambdaInit: 1x Invoke with N events LambdaInit --> EventHandler state EventHandler { From 1ebc157a079ee831862abfb8f51d05f58f8a7dc8 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 11:55:31 +0200 Subject: [PATCH 61/75] docs: add diagram where n+1 problem shifts to Lambda runtime --- docs/core/event_handler/appsync.md | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index c51ca5c6f2e..9a7bbb44a61 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -350,6 +350,49 @@ We support AWS Appsync's batching mechanism for Lambda Resolvers. It prevents mu #### Processing Batch items individually +```mermaid +stateDiagram-v2 + direction LR + LambdaInit: Lambda invocation + EventHandler: Event Handler + EventHandlerResolver: Route event based on GraphQL type/field keys + Client: Client query (getPosts) + YourLogic: Call your registered resolver function N times + EventHandlerResolverErrorHandling: Gracefully handle errors with null response + EventHandlerResolverBuilder: Aggregate responses to match batch size + AppSyncBatchPostsResolution: query getPosts + AppSyncBatchPostsItems: get all posts data (id, title, relatedPosts) + AppSyncBatchRelatedPosts: get related posts (id, title, relatedPosts) + AppSyncBatchAggregate: aggregate batch resolver event + AppSyncBatchLimit: reached batch size limit + LambdaResponse: Lambda response + + Client --> AppSyncBatchResolverMode + state AppSyncBatchResolverMode { + [*] --> AppSyncBatchPostsResolution + AppSyncBatchPostsResolution --> AppSyncBatchPostsItems + AppSyncBatchPostsItems --> AppSyncBatchRelatedPosts: N additional queries + AppSyncBatchRelatedPosts --> AppSyncBatchRelatedPosts + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchAggregate --> AppSyncBatchLimit + } + + AppSyncBatchResolverMode --> LambdaInit: 1x Invoke with N events + LambdaInit --> EventHandler + + state EventHandler { + [*] --> EventHandlerResolver: app.resolve(event, context) + EventHandlerResolver --> YourLogic + YourLogic --> EventHandlerResolverErrorHandling + EventHandlerResolverErrorHandling --> EventHandlerResolverBuilder + EventHandlerResolverBuilder --> LambdaResponse + } +``` + +
Batch resolvers: reducing Lambda invokes but fetching data N times (similar to single resolver).
+ You can process each item in the batch individually, and we can handle exceptions for you. However, it's important to note that utilizing this method may increase the execution time of your Lambda function. ???+ tip From de73d47b61910e82f305b5b81697b6fffd397467 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 12:07:54 +0200 Subject: [PATCH 62/75] docs: add diagram where n+1 problem shifts to Lambda runtime w/ error handling --- docs/core/event_handler/appsync.md | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 9a7bbb44a61..f4e56a97508 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -406,6 +406,65 @@ You can process each item in the batch individually, and we can handle exception 1. You need to disable the aggregated event by using `aggregate` flag. The resolver receives and processes each record one at a time. +=== "getting_started_with_batch_resolver_payload.json" + ```json hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" + ``` + +##### Raise on error + +!!! todo "Explanation about hard failure" + +```mermaid +stateDiagram-v2 + direction LR + LambdaInit: Lambda invocation + EventHandler: Event Handler + EventHandlerResolver: Route event based on GraphQL type/field keys + Client: Client query (getPosts) + YourLogic: Call your registered resolver function N times + EventHandlerResolverErrorHandling: Error? + EventHandlerResolverHappyPath: No error? + EventHandlerResolverUnhappyPath: Propagate any exception + EventHandlerResolverBuilder: Aggregate responses to match batch size + AppSyncBatchPostsResolution: query getPosts + AppSyncBatchPostsItems: get all posts data (id, title, relatedPosts) + AppSyncBatchRelatedPosts: get related posts (id, title, relatedPosts) + AppSyncBatchAggregate: aggregate batch resolver event + AppSyncBatchLimit: reached batch size limit + LambdaResponse: Lambda response + LambdaErrorResponse: Lambda error + + Client --> AppSyncBatchResolverMode + state AppSyncBatchResolverMode { + [*] --> AppSyncBatchPostsResolution + AppSyncBatchPostsResolution --> AppSyncBatchPostsItems + AppSyncBatchPostsItems --> AppSyncBatchRelatedPosts: N additional queries + AppSyncBatchRelatedPosts --> AppSyncBatchRelatedPosts + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchRelatedPosts --> AppSyncBatchAggregate + AppSyncBatchAggregate --> AppSyncBatchLimit + } + + AppSyncBatchResolverMode --> LambdaInit: 1x Invoke with N events + LambdaInit --> EventHandler + + state EventHandler { + [*] --> EventHandlerResolver: app.resolve(event, context) + EventHandlerResolver --> YourLogic + YourLogic --> EventHandlerResolverHappyPath + YourLogic --> EventHandlerResolverErrorHandling + EventHandlerResolverHappyPath --> EventHandlerResolverBuilder + EventHandlerResolverErrorHandling --> EventHandlerResolverUnhappyPath + EventHandlerResolverUnhappyPath --> LambdaErrorResponse + + EventHandlerResolverBuilder --> LambdaResponse + } +``` + +
Batch resolvers: reducing Lambda invokes but fetching data N times (similar to single resolver).
+ === "getting_started_with_batch_resolver_handling_error.py" ```python hl_lines="3 7 17" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py" From 4414d6bfe218655f2677c88780ca20db96908fdb Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 12:08:37 +0200 Subject: [PATCH 63/75] docs: highlight lambda response for non-errors --- docs/core/event_handler/appsync.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index f4e56a97508..5700b482681 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -432,7 +432,7 @@ stateDiagram-v2 AppSyncBatchRelatedPosts: get related posts (id, title, relatedPosts) AppSyncBatchAggregate: aggregate batch resolver event AppSyncBatchLimit: reached batch size limit - LambdaResponse: Lambda response + LambdaResponse: Lambda response LambdaErrorResponse: Lambda error Client --> AppSyncBatchResolverMode From 376481972c71db6b02ef7752fc421926cb082f42 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 13:23:17 +0200 Subject: [PATCH 64/75] docs(setup): increase table of contents depth to 5 to help redis and other subsections --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index efd18efd5cc..d2bab86cd22 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -116,7 +116,7 @@ markdown_extensions: - meta - toc: permalink: true - toc_depth: 4 + toc_depth: 5 - attr_list - md_in_html - pymdownx.emoji: From 8606136cb9b7b9567015a0b6bebb2907a3d4bfce Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 26 Jun 2024 13:28:45 +0100 Subject: [PATCH 65/75] Adding examples --- ...tting_started_with_batch_async_resolver.py | 33 ++++++++++--------- .../getting_started_with_batch_resolver.py | 27 +++++++-------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py index ef505be7776..55d2e1093cd 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py @@ -1,14 +1,11 @@ -from typing import Any, List, Optional +from typing import Any, Dict, List -from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext -logger = Logger() app = AppSyncResolver() - posts_related = { "1": {"title": "post1"}, "2": {"title": "post2"}, @@ -16,19 +13,23 @@ } +def search_batch_posts(posts: List) -> Dict[str, Any]: + return {post_id: posts_related.get(post_id) for post_id in posts} + + @app.async_batch_resolver(type_name="Query", field_name="relatedPosts") -async def related_posts(event: List[AppSyncResolverEvent]) -> Optional[List[Any]]: - results = [] - - for record in event: # (1)! - post_id = record.arguments.get("post_id") - try: - results.append(posts_related[post_id] if post_id else None) - # Add other logic here - except Exception: - logger.error("Error processing record", post_id=post_id) - - return results +async def related_posts(event: List[AppSyncResolverEvent]) -> List[Any]: + # Extract all post_ids in order + post_ids = [record.arguments.get("post_id") for record in event] + + # Get unique post_ids while preserving order + unique_post_ids = list(dict.fromkeys(post_ids)) + + # Fetch posts in a single batch operation + fetched_posts: Dict = search_batch_posts(unique_post_ids) + + # Return results in original order + return [fetched_posts.get(post_id) for post_id in post_ids] def lambda_handler(event, context: LambdaContext) -> dict: diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py index 6ef8005d72d..41b3493b2a7 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py @@ -1,14 +1,11 @@ -from typing import Any, List +from typing import Any, Dict, List -from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.typing import LambdaContext -logger = Logger() app = AppSyncResolver() - posts_related = { "1": {"title": "post1"}, "2": {"title": "post2"}, @@ -16,19 +13,23 @@ } +def search_batch_posts(posts: List) -> Dict[str, Any]: + return {post_id: posts_related.get(post_id) for post_id in posts} + + @app.batch_resolver(type_name="Query", field_name="relatedPosts") def related_posts(event: List[AppSyncResolverEvent]) -> List[Any]: - results = [] + # Extract all post_ids in order + post_ids = [record.arguments.get("post_id") for record in event] + + # Get unique post_ids while preserving order + unique_post_ids = list(dict.fromkeys(post_ids)) - for record in event: # (1)! - post_id = record.arguments.get("post_id") - try: - results.append(posts_related[post_id] if post_id else None) - # Add other logic here - except Exception: - logger.error("Error processing record", post_id=post_id) + # Fetch posts in a single batch operation + fetched_posts: Dict = search_batch_posts(unique_post_ids) - return results + # Return results in original order + return [fetched_posts.get(post_id) for post_id in post_ids] def lambda_handler(event, context: LambdaContext) -> dict: From 4c84e5235c498c8afd3ce382c3126e5afa27349f Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 15:11:46 +0200 Subject: [PATCH 66/75] docs: explain N+1 problem and organize content into sub-sections --- docs/core/event_handler/appsync.md | 56 ++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 5700b482681..0ee4b655c6e 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -296,10 +296,10 @@ stateDiagram-v2 LambdaInit: Lambda invocation EventHandler: Event Handler EventHandlerResolver: Route event based on GraphQL type/field keys - Client: Client query (getPosts) + Client: Client query (listPosts) YourLogic: Run your registered resolver function EventHandlerResolverBuilder: Verifies response is a list - AppSyncBatchPostsResolution: query getPosts + AppSyncBatchPostsResolution: query listPosts AppSyncBatchPostsItems: get all posts data (id, title, relatedPosts) AppSyncBatchRelatedPosts: get related posts (id, title, relatedPosts) AppSyncBatchAggregate: aggregate batch resolver event @@ -329,12 +329,46 @@ stateDiagram-v2 } ``` -
Batch resolvers: visualizing N+1 in `relatedPosts` field.
+
Batch resolvers mechanics: visualizing N+1 in `relatedPosts` field.
-We support AWS Appsync's batching mechanism for Lambda Resolvers. It prevents multiple Lambda executions by grouping events and using the `@batch_resolver` or `@async_batch_resolver` decorators to resolve the entire batch. +#### Understanding N+1 problem -???+ info - If you want to understand more how to configure batch processing for the AppSync, please follow this [guide](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"}. +When AWS AppSync has [batching enabled for Lambda Resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#advanced-use-case-batching){target="_blank"}, it will group as many requests as possible before invoking your Lambda invocation. Effectively solving the [N+1 problem in GraphQL](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/){target="_blank"}. + +For example, say you have a query named `listPosts`. For each post, you also want `relatedPosts`. **Without batching**, AppSync will: + +1. Invoke your Lambda function to get the first post +2. Invoke your Lambda function for each related post +3. Repeat 1 until done + +```mermaid +sequenceDiagram + participant Client + participant AppSync + participant Lambda + participant Database + + Client->>AppSync: GraphQL Query + Note over Client,AppSync: query listPosts {
id
title
relatedPosts { id title }
} + + AppSync->>Lambda: Fetch N posts (listPosts) + Lambda->>Database: Query + Database->>Lambda: Posts + Lambda-->>AppSync: Return posts (id, title) + loop Fetch N related posts (relatedPosts) + AppSync->>Lambda: Invoke function (N times) + Lambda->>Database: Query + Database-->>Lambda: Return related posts + Lambda-->>AppSync: Return related posts + end + AppSync-->>Client: Return posts and their related posts +``` + +#### Batch resolvers + +You can use `@batch_resolver` or `@async_batch_resolver` decorators to receive the entire batch of requests. + +In this mode, you must return results in the same order of your batch items, so AppSync can associate the results. === "getting_started_with_batch_resolver.py" ```python hl_lines="3 7 17" @@ -348,7 +382,7 @@ We support AWS Appsync's batching mechanism for Lambda Resolvers. It prevents mu --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` -#### Processing Batch items individually +#### Processing items individually ```mermaid stateDiagram-v2 @@ -356,11 +390,11 @@ stateDiagram-v2 LambdaInit: Lambda invocation EventHandler: Event Handler EventHandlerResolver: Route event based on GraphQL type/field keys - Client: Client query (getPosts) + Client: Client query (listPosts) YourLogic: Call your registered resolver function N times EventHandlerResolverErrorHandling: Gracefully handle errors with null response EventHandlerResolverBuilder: Aggregate responses to match batch size - AppSyncBatchPostsResolution: query getPosts + AppSyncBatchPostsResolution: query listPosts AppSyncBatchPostsItems: get all posts data (id, title, relatedPosts) AppSyncBatchRelatedPosts: get related posts (id, title, relatedPosts) AppSyncBatchAggregate: aggregate batch resolver event @@ -421,13 +455,13 @@ stateDiagram-v2 LambdaInit: Lambda invocation EventHandler: Event Handler EventHandlerResolver: Route event based on GraphQL type/field keys - Client: Client query (getPosts) + Client: Client query (listPosts) YourLogic: Call your registered resolver function N times EventHandlerResolverErrorHandling: Error? EventHandlerResolverHappyPath: No error? EventHandlerResolverUnhappyPath: Propagate any exception EventHandlerResolverBuilder: Aggregate responses to match batch size - AppSyncBatchPostsResolution: query getPosts + AppSyncBatchPostsResolution: query listPosts AppSyncBatchPostsItems: get all posts data (id, title, relatedPosts) AppSyncBatchRelatedPosts: get related posts (id, title, relatedPosts) AppSyncBatchAggregate: aggregate batch resolver event From 06480d4b23d1954adfd17fd060c368596359717f Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 15:26:29 +0200 Subject: [PATCH 67/75] docs: clean up batch resolvers section; add typing --- docs/core/event_handler/appsync.md | 7 ++++--- .../src/getting_started_with_batch_resolver.py | 15 +++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 0ee4b655c6e..926bfe12b56 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -368,14 +368,15 @@ sequenceDiagram You can use `@batch_resolver` or `@async_batch_resolver` decorators to receive the entire batch of requests. -In this mode, you must return results in the same order of your batch items, so AppSync can associate the results. +In this mode, you must return results in the same order of your batch items, so AppSync can associate the results back to the client. === "getting_started_with_batch_resolver.py" - ```python hl_lines="3 7 17" + ```python hl_lines="5 9 23" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver.py" ``` - 1. The entire batch is sent to the resolver, and you need to iterate through it to process all records. + 1. The entire batch is sent to the resolver. You need to iterate through it to process all records. + 2. We use `post_id` as our unique identifier of the GraphQL request. === "getting_started_with_batch_resolver_payload.json" ```json hl_lines="4 16 21 29 41 46" diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py index 41b3493b2a7..c6d5285dc55 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, List +from __future__ import annotations + +from typing import Any from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent @@ -6,6 +8,7 @@ app = AppSyncResolver() +# mimic DB data for simplicity posts_related = { "1": {"title": "post1"}, "2": {"title": "post2"}, @@ -13,20 +16,20 @@ } -def search_batch_posts(posts: List) -> Dict[str, Any]: +def search_batch_posts(posts: list) -> dict[str, Any]: return {post_id: posts_related.get(post_id) for post_id in posts} @app.batch_resolver(type_name="Query", field_name="relatedPosts") -def related_posts(event: List[AppSyncResolverEvent]) -> List[Any]: +def related_posts(event: list[AppSyncResolverEvent]) -> list[Any]: # (1)! # Extract all post_ids in order - post_ids = [record.arguments.get("post_id") for record in event] + post_ids = [record.arguments.get("post_id") for record in event] # (2)! # Get unique post_ids while preserving order - unique_post_ids = list(dict.fromkeys(post_ids)) + unique_post_ids: list[str] = list(dict.fromkeys(post_ids)) # Fetch posts in a single batch operation - fetched_posts: Dict = search_batch_posts(unique_post_ids) + fetched_posts = search_batch_posts(unique_post_ids) # Return results in original order return [fetched_posts.get(post_id) for post_id in post_ids] From 925040ed971eaddd7f320b5748b8bb39f55a8fd7 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 16:06:38 +0200 Subject: [PATCH 68/75] docs: clean up no-aggregate processing section --- docs/core/event_handler/appsync.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 926bfe12b56..affa5ee660b 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -428,10 +428,17 @@ stateDiagram-v2
Batch resolvers: reducing Lambda invokes but fetching data N times (similar to single resolver).
-You can process each item in the batch individually, and we can handle exceptions for you. However, it's important to note that utilizing this method may increase the execution time of your Lambda function. +In rare scenarios, you might want to process each item individually, trading ease of use for increased latency as you handle one batch item at a time. -???+ tip - For better error handling, you may need to configure response mapping templates and specify error keys. Explore more on returning individual errors [here](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#advanced-use-case-batching){target="_blank"}. +You can toggle `aggregate` parameter in `@batch_resolver` parameter for your resolver function to be called N times. + +!!! note "This does not resolve the N+1 problem, but shifts it to the Lambda runtime." + +In this mode, we will: + +1. Aggregate each response we receive from your function in the exact order it receives +2. Gracefully handle errors by adding `None` in the final response for each batch item that failed processing + * You can customize `nul` or error responses back to the client in the [AppSync resolver mapping templates](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#returning-individual-errors){target="_blank"} === "getting_started_with_batch_resolver_individual.py" ```python hl_lines="3 7 17" From 2428ebd8617026f9e67425f601761298dea76110 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 16:12:27 +0200 Subject: [PATCH 69/75] docs: clean up raise on error section --- docs/core/event_handler/appsync.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index affa5ee660b..8d175802b1f 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -430,7 +430,7 @@ stateDiagram-v2 In rare scenarios, you might want to process each item individually, trading ease of use for increased latency as you handle one batch item at a time. -You can toggle `aggregate` parameter in `@batch_resolver` parameter for your resolver function to be called N times. +You can toggle `aggregate` parameter in `@batch_resolver` decorator for your resolver function to be called N times. !!! note "This does not resolve the N+1 problem, but shifts it to the Lambda runtime." @@ -455,8 +455,6 @@ In this mode, we will: ##### Raise on error -!!! todo "Explanation about hard failure" - ```mermaid stateDiagram-v2 direction LR @@ -507,6 +505,10 @@ stateDiagram-v2
Batch resolvers: reducing Lambda invokes but fetching data N times (similar to single resolver).
+You can toggle `raise_on_error` parameter in `@batch_resolver` to propagate any exception instead of gracefully returning `None` for a given batch item. + +This is useful when you want to stop processing immediately in the event of an unhandled or unrecoverable exception. + === "getting_started_with_batch_resolver_handling_error.py" ```python hl_lines="3 7 17" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py" From 92db4a268a84a88c8ff94c3ab4080f37294a8eb3 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 26 Jun 2024 15:17:54 +0100 Subject: [PATCH 70/75] Adding examples --- .../data_classes/appsync_resolver_event.py | 4 +- docs/core/event_handler/appsync.md | 20 ++++ ...tting_started_with_batch_async_resolver.py | 13 ++- .../getting_started_with_batch_query.graphql | 12 ++ .../getting_started_with_batch_resolver.py | 4 +- ...g_started_with_batch_resolver_payload.json | 109 ++++++++++-------- 6 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_query.graphql diff --git a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py index f58308377ff..d3166db1e3b 100644 --- a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py +++ b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py @@ -184,9 +184,9 @@ def identity(self) -> Union[None, AppSyncIdentityIAM, AppSyncIdentityCognito]: return get_identity_object(self.get("identity")) @property - def source(self) -> Optional[Dict[str, Any]]: + def source(self) -> Dict[str, Any]: """A map that contains the resolution of the parent field.""" - return self.get("source") + return self.get("source") or {} @property def request_headers(self) -> Dict[str, str]: diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 8d175802b1f..07005313f0a 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -383,6 +383,11 @@ In this mode, you must return results in the same order of your batch items, so --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` +=== "getting_started_with_batch_query.graphql" + ```typescript hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + ``` + #### Processing items individually ```mermaid @@ -453,6 +458,11 @@ In this mode, we will: --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` +=== "getting_started_with_batch_query.graphql" + ```typescript hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + ``` + ##### Raise on error ```mermaid @@ -521,6 +531,11 @@ This is useful when you want to stop processing immediately in the event of an u --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` +=== "getting_started_with_batch_query.graphql" + ```typescript hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + ``` + #### Async Choose the asynchronous batch processor when your objective is to leverage concurrency capabilities without the necessity of preserving records in a specific order. @@ -538,6 +553,11 @@ Choose the asynchronous batch processor when your objective is to leverage concu --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json" ``` +=== "getting_started_with_batch_query.graphql" + ```typescript hl_lines="4 16 21 29 41 46" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + ``` + ## Testing your code You can test your resolvers by passing a mocked or actual AppSync Lambda event that you're expecting. diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py index 55d2e1093cd..28f9dafa648 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, List +from __future__ import annotations + +from typing import Any from aws_lambda_powertools.event_handler import AppSyncResolver from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent @@ -6,6 +8,7 @@ app = AppSyncResolver() +# mimic DB data for simplicity posts_related = { "1": {"title": "post1"}, "2": {"title": "post2"}, @@ -13,20 +16,20 @@ } -def search_batch_posts(posts: List) -> Dict[str, Any]: +def search_batch_posts(posts: list) -> dict[str, Any]: return {post_id: posts_related.get(post_id) for post_id in posts} @app.async_batch_resolver(type_name="Query", field_name="relatedPosts") -async def related_posts(event: List[AppSyncResolverEvent]) -> List[Any]: +async def related_posts(event: list[AppSyncResolverEvent]) -> list[Any]: # (1)! # Extract all post_ids in order - post_ids = [record.arguments.get("post_id") for record in event] + post_ids: list = [record.source.get("post_id") for record in event] # (2)! # Get unique post_ids while preserving order unique_post_ids = list(dict.fromkeys(post_ids)) # Fetch posts in a single batch operation - fetched_posts: Dict = search_batch_posts(unique_post_ids) + fetched_posts = search_batch_posts(unique_post_ids) # Return results in original order return [fetched_posts.get(post_id) for post_id in post_ids] diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_query.graphql b/examples/event_handler_graphql/src/getting_started_with_batch_query.graphql new file mode 100644 index 00000000000..d89358dcde5 --- /dev/null +++ b/examples/event_handler_graphql/src/getting_started_with_batch_query.graphql @@ -0,0 +1,12 @@ +query MyQuery { + getPost(post_id: "2") { + relatedPosts { + post_id + author + relatedPosts { + post_id + author + } + } + } +} diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py index c6d5285dc55..653ce59775e 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py @@ -23,10 +23,10 @@ def search_batch_posts(posts: list) -> dict[str, Any]: @app.batch_resolver(type_name="Query", field_name="relatedPosts") def related_posts(event: list[AppSyncResolverEvent]) -> list[Any]: # (1)! # Extract all post_ids in order - post_ids = [record.arguments.get("post_id") for record in event] # (2)! + post_ids: list = [record.source.get("post_id") for record in event] # (2)! # Get unique post_ids while preserving order - unique_post_ids: list[str] = list(dict.fromkeys(post_ids)) + unique_post_ids = list(dict.fromkeys(post_ids)) # Fetch posts in a single batch operation fetched_posts = search_batch_posts(unique_post_ids) diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json index f0a3c147d86..6f4aaa2ff20 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json +++ b/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json @@ -1,52 +1,59 @@ [ - { - "arguments":{ - "post_id":"1" - }, - "identity":"None", - "source":"None", - "request":{ - "headers":{ - "x-forwarded-for":"18.68.31.36", - "sec-ch-ua-mobile":"?0" - } - }, - "prev":"None", - "info":{ - "fieldName":"relatedPosts", - "selectionSetList":[ - "relatedPosts" - ], - "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", - "parentTypeName":"Query", - "variables":{ - - } - } - }, - { - "arguments":{ - "post_id":"2" - }, - "identity":"None", - "source":"None", - "request":{ - "headers":{ - "x-forwarded-for":"18.68.31.36", - "sec-ch-ua-mobile":"?0" - } - }, - "prev":"None", - "info":{ - "fieldName":"relatedPosts", - "selectionSetList":[ - "relatedPosts" - ], - "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", - "parentTypeName":"Query", - "variables":{ - - } - } - } - ] + { + "arguments":{}, + "identity":"None", + "source":{ + "post_id":"1", + "author":"Author1" + }, + "prev":"None", + "info":{ + "selectionSetList":[ + "post_id", + "author" + ], + "selectionSetGraphQL":"{\n post_id\n author\n}", + "fieldName":"relatedPosts", + "parentTypeName":"Post", + "variables":{} + } + }, + { + "arguments":{}, + "identity":"None", + "source":{ + "post_id":"2", + "author":"Author2" + }, + "prev":"None", + "info":{ + "selectionSetList":[ + "post_id", + "author" + ], + "selectionSetGraphQL":"{\n post_id\n author\n}", + "fieldName":"relatedPosts", + "parentTypeName":"Post", + "variables":{} + } + }, + { + "arguments":{}, + "identity":"None", + "source":{ + "post_id":"1", + "author":"Author1" + }, + "prev":"None", + "info":{ + "selectionSetList":[ + "post_id", + "author" + ], + "selectionSetGraphQL":"{\n post_id\n author\n}", + "fieldName":"relatedPosts", + "parentTypeName":"Post", + "variables":{} + } + } +] From 8f3af70c01678357cd8b5967467a5478fb12ea87 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 16:18:52 +0200 Subject: [PATCH 71/75] docs: clean up async section --- docs/core/event_handler/appsync.md | 5 +---- .../src/getting_started_with_batch_async_resolver.py | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 07005313f0a..a0243c582b3 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -538,10 +538,7 @@ This is useful when you want to stop processing immediately in the event of an u #### Async -Choose the asynchronous batch processor when your objective is to leverage concurrency capabilities without the necessity of preserving records in a specific order. - -???+ warning - Make sure that preserving the order of the response is not a requirement of your logic. +Similar to `@batch_resolver` explained in [batch resolvers](#batch-resolvers), you can use `async_batch_resolver` to handle async functions. === "getting_started_with_batch_async_resolver.py" ```python hl_lines="3 7 17" diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py index 28f9dafa648..2d3f6777eeb 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py @@ -16,7 +16,7 @@ } -def search_batch_posts(posts: list) -> dict[str, Any]: +async def search_batch_posts(posts: list) -> dict[str, Any]: return {post_id: posts_related.get(post_id) for post_id in posts} @@ -29,7 +29,7 @@ async def related_posts(event: list[AppSyncResolverEvent]) -> list[Any]: # (1)! unique_post_ids = list(dict.fromkeys(post_ids)) # Fetch posts in a single batch operation - fetched_posts = search_batch_posts(unique_post_ids) + fetched_posts = await search_batch_posts(unique_post_ids) # Return results in original order return [fetched_posts.get(post_id) for post_id in post_ids] From bc6cd12405c061e17bcd1beff18a524acba028ad Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 16:34:15 +0200 Subject: [PATCH 72/75] docs: fix highlights, add missing code annotation --- docs/core/event_handler/appsync.md | 32 ++++++------ ...tting_started_with_batch_async_resolver.py | 6 +-- ...ted_with_batch_async_resolver_payload.json | 52 ------------------- 3 files changed, 20 insertions(+), 70 deletions(-) delete mode 100644 examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index a0243c582b3..30326b60bdf 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -379,16 +379,16 @@ In this mode, you must return results in the same order of your batch items, so 2. We use `post_id` as our unique identifier of the GraphQL request. === "getting_started_with_batch_resolver_payload.json" - ```json hl_lines="4 16 21 29 41 46" + ```json hl_lines="6 16 25 35 44 54" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` === "getting_started_with_batch_query.graphql" - ```typescript hl_lines="4 16 21 29 41 46" + ```typescript hl_lines="3 6" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" ``` -#### Processing items individually +##### Processing items individually ```mermaid stateDiagram-v2 @@ -446,7 +446,7 @@ In this mode, we will: * You can customize `nul` or error responses back to the client in the [AppSync resolver mapping templates](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#returning-individual-errors){target="_blank"} === "getting_started_with_batch_resolver_individual.py" - ```python hl_lines="3 7 17" + ```python hl_lines="5 9 19" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py" ``` @@ -454,12 +454,12 @@ In this mode, we will: The resolver receives and processes each record one at a time. === "getting_started_with_batch_resolver_payload.json" - ```json hl_lines="4 16 21 29 41 46" + ```json hl_lines="6 16 25 35 44 54" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` === "getting_started_with_batch_query.graphql" - ```typescript hl_lines="4 16 21 29 41 46" + ```typescript hl_lines="3 6" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" ``` @@ -520,38 +520,40 @@ You can toggle `raise_on_error` parameter in `@batch_resolver` to propagate any This is useful when you want to stop processing immediately in the event of an unhandled or unrecoverable exception. === "getting_started_with_batch_resolver_handling_error.py" - ```python hl_lines="3 7 17" + ```python hl_lines="5 9 19" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py" ``` 1. You can enable enable the error handling by using `raise_on_error` flag. === "getting_started_with_batch_resolver_payload.json" - ```json hl_lines="4 16 21 29 41 46" + ```json hl_lines="6 16 25 35 44 54" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` === "getting_started_with_batch_query.graphql" - ```typescript hl_lines="4 16 21 29 41 46" + ```typescript hl_lines="3 6" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" ``` -#### Async +#### Async batch resolver Similar to `@batch_resolver` explained in [batch resolvers](#batch-resolvers), you can use `async_batch_resolver` to handle async functions. === "getting_started_with_batch_async_resolver.py" - ```python hl_lines="3 7 17" + ```python hl_lines="5 9 23" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py" ``` -=== "getting_started_with_batch_async_resolver_payload.json" - ```json hl_lines="4 16 21 29 41 46" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json" + 1. `async_batch_resolver` takes care of running and waiting for coroutine completion. + +=== "getting_started_with_batch_resolver_payload.json" + ```json hl_lines="6 16 25 35 44 54" + --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" ``` === "getting_started_with_batch_query.graphql" - ```typescript hl_lines="4 16 21 29 41 46" + ```typescript hl_lines="3 6" --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" ``` diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py index 2d3f6777eeb..e56802cf0c6 100644 --- a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py +++ b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py @@ -21,9 +21,9 @@ async def search_batch_posts(posts: list) -> dict[str, Any]: @app.async_batch_resolver(type_name="Query", field_name="relatedPosts") -async def related_posts(event: list[AppSyncResolverEvent]) -> list[Any]: # (1)! +async def related_posts(event: list[AppSyncResolverEvent]) -> list[Any]: # Extract all post_ids in order - post_ids: list = [record.source.get("post_id") for record in event] # (2)! + post_ids: list = [record.source.get("post_id") for record in event] # Get unique post_ids while preserving order unique_post_ids = list(dict.fromkeys(post_ids)) @@ -36,4 +36,4 @@ async def related_posts(event: list[AppSyncResolverEvent]) -> list[Any]: # (1)! def lambda_handler(event, context: LambdaContext) -> dict: - return app.resolve(event, context) + return app.resolve(event, context) # (1)! diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json b/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json deleted file mode 100644 index 06bbb2daaae..00000000000 --- a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver_payload.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "arguments":{ - "post_id":"1" - }, - "identity":"None", - "source":"None", - "request":{ - "headers":{ - "x-forwarded-for":"18.68.31.36", - "sec-ch-ua-mobile":"?0" - } - }, - "prev":"None", - "info":{ - "fieldName":"relatedPosts", - "selectionSetList":[ - "relatedPosts" - ], - "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", - "parentTypeName":"Query", - "variables":{ - - } - } - }, - { - "arguments":{ - "post_id":"10" - }, - "identity":"None", - "source":"None", - "request":{ - "headers":{ - "x-forwarded-for":"18.68.31.36", - "sec-ch-ua-mobile":"?0" - } - }, - "prev":"None", - "info":{ - "fieldName":"relatedPosts", - "selectionSetList":[ - "relatedPosts" - ], - "selectionSetGraphQL":"{\n relatedPosts {\n downs\n content\n relatedPosts {\n ups\n downs\n relatedPosts {\n content\n downs\n }\n }\n }\n}", - "parentTypeName":"Query", - "variables":{ - - } - } - } - ] From a4173e06c2cc9720fde3401c0657b50f499ad0ee Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 26 Jun 2024 16:47:40 +0200 Subject: [PATCH 73/75] docs: rename snippets to match advanced section --- docs/core/event_handler/appsync.md | 48 +++++++++---------- ...er.py => advanced_batch_async_resolver.py} | 0 ...y.graphql => advanced_batch_query.graphql} | 0 ...resolver.py => advanced_batch_resolver.py} | 0 ...advanced_batch_resolver_handling_error.py} | 0 ... => advanced_batch_resolver_individual.py} | 0 ...n => advanced_batch_resolver_payload.json} | 0 7 files changed, 24 insertions(+), 24 deletions(-) rename examples/event_handler_graphql/src/{getting_started_with_batch_async_resolver.py => advanced_batch_async_resolver.py} (100%) rename examples/event_handler_graphql/src/{getting_started_with_batch_query.graphql => advanced_batch_query.graphql} (100%) rename examples/event_handler_graphql/src/{getting_started_with_batch_resolver.py => advanced_batch_resolver.py} (100%) rename examples/event_handler_graphql/src/{getting_started_with_batch_resolver_handling_error.py => advanced_batch_resolver_handling_error.py} (100%) rename examples/event_handler_graphql/src/{getting_started_with_batch_resolver_individual.py => advanced_batch_resolver_individual.py} (100%) rename examples/event_handler_graphql/src/{getting_started_with_batch_resolver_payload.json => advanced_batch_resolver_payload.json} (100%) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 30326b60bdf..440940cd107 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -370,22 +370,22 @@ You can use `@batch_resolver` or `@async_batch_resolver` decorators to receive t In this mode, you must return results in the same order of your batch items, so AppSync can associate the results back to the client. -=== "getting_started_with_batch_resolver.py" +=== "advanced_batch_resolver.py" ```python hl_lines="5 9 23" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver.py" + --8<-- "examples/event_handler_graphql/src/advanced_batch_resolver.py" ``` 1. The entire batch is sent to the resolver. You need to iterate through it to process all records. 2. We use `post_id` as our unique identifier of the GraphQL request. -=== "getting_started_with_batch_resolver_payload.json" +=== "advanced_batch_resolver_payload.json" ```json hl_lines="6 16 25 35 44 54" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" + --8<-- "examples/event_handler_graphql/src/advanced_batch_resolver_payload.json" ``` -=== "getting_started_with_batch_query.graphql" +=== "advanced_batch_query.graphql" ```typescript hl_lines="3 6" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + --8<-- "examples/event_handler_graphql/src/advanced_batch_query.graphql" ``` ##### Processing items individually @@ -445,22 +445,22 @@ In this mode, we will: 2. Gracefully handle errors by adding `None` in the final response for each batch item that failed processing * You can customize `nul` or error responses back to the client in the [AppSync resolver mapping templates](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#returning-individual-errors){target="_blank"} -=== "getting_started_with_batch_resolver_individual.py" +=== "advanced_batch_resolver_individual.py" ```python hl_lines="5 9 19" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py" + --8<-- "examples/event_handler_graphql/src/advanced_batch_resolver_individual.py" ``` 1. You need to disable the aggregated event by using `aggregate` flag. The resolver receives and processes each record one at a time. -=== "getting_started_with_batch_resolver_payload.json" +=== "advanced_batch_resolver_payload.json" ```json hl_lines="6 16 25 35 44 54" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" + --8<-- "examples/event_handler_graphql/src/advanced_batch_resolver_payload.json" ``` -=== "getting_started_with_batch_query.graphql" +=== "advanced_batch_query.graphql" ```typescript hl_lines="3 6" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + --8<-- "examples/event_handler_graphql/src/advanced_batch_query.graphql" ``` ##### Raise on error @@ -519,42 +519,42 @@ You can toggle `raise_on_error` parameter in `@batch_resolver` to propagate any This is useful when you want to stop processing immediately in the event of an unhandled or unrecoverable exception. -=== "getting_started_with_batch_resolver_handling_error.py" +=== "advanced_batch_resolver_handling_error.py" ```python hl_lines="5 9 19" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py" + --8<-- "examples/event_handler_graphql/src/advanced_batch_resolver_handling_error.py" ``` 1. You can enable enable the error handling by using `raise_on_error` flag. -=== "getting_started_with_batch_resolver_payload.json" +=== "advanced_batch_resolver_payload.json" ```json hl_lines="6 16 25 35 44 54" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" + --8<-- "examples/event_handler_graphql/src/advanced_batch_resolver_payload.json" ``` -=== "getting_started_with_batch_query.graphql" +=== "advanced_batch_query.graphql" ```typescript hl_lines="3 6" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + --8<-- "examples/event_handler_graphql/src/advanced_batch_query.graphql" ``` #### Async batch resolver Similar to `@batch_resolver` explained in [batch resolvers](#batch-resolvers), you can use `async_batch_resolver` to handle async functions. -=== "getting_started_with_batch_async_resolver.py" +=== "advanced_batch_async_resolver.py" ```python hl_lines="5 9 23" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py" + --8<-- "examples/event_handler_graphql/src/advanced_batch_async_resolver.py" ``` 1. `async_batch_resolver` takes care of running and waiting for coroutine completion. -=== "getting_started_with_batch_resolver_payload.json" +=== "advanced_batch_resolver_payload.json" ```json hl_lines="6 16 25 35 44 54" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json" + --8<-- "examples/event_handler_graphql/src/advanced_batch_resolver_payload.json" ``` -=== "getting_started_with_batch_query.graphql" +=== "advanced_batch_query.graphql" ```typescript hl_lines="3 6" - --8<-- "examples/event_handler_graphql/src/getting_started_with_batch_query.graphql" + --8<-- "examples/event_handler_graphql/src/advanced_batch_query.graphql" ``` ## Testing your code diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py b/examples/event_handler_graphql/src/advanced_batch_async_resolver.py similarity index 100% rename from examples/event_handler_graphql/src/getting_started_with_batch_async_resolver.py rename to examples/event_handler_graphql/src/advanced_batch_async_resolver.py diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_query.graphql b/examples/event_handler_graphql/src/advanced_batch_query.graphql similarity index 100% rename from examples/event_handler_graphql/src/getting_started_with_batch_query.graphql rename to examples/event_handler_graphql/src/advanced_batch_query.graphql diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver.py b/examples/event_handler_graphql/src/advanced_batch_resolver.py similarity index 100% rename from examples/event_handler_graphql/src/getting_started_with_batch_resolver.py rename to examples/event_handler_graphql/src/advanced_batch_resolver.py diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py b/examples/event_handler_graphql/src/advanced_batch_resolver_handling_error.py similarity index 100% rename from examples/event_handler_graphql/src/getting_started_with_batch_resolver_handling_error.py rename to examples/event_handler_graphql/src/advanced_batch_resolver_handling_error.py diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py b/examples/event_handler_graphql/src/advanced_batch_resolver_individual.py similarity index 100% rename from examples/event_handler_graphql/src/getting_started_with_batch_resolver_individual.py rename to examples/event_handler_graphql/src/advanced_batch_resolver_individual.py diff --git a/examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json b/examples/event_handler_graphql/src/advanced_batch_resolver_payload.json similarity index 100% rename from examples/event_handler_graphql/src/getting_started_with_batch_resolver_payload.json rename to examples/event_handler_graphql/src/advanced_batch_resolver_payload.json From 5d3616ad7d3e2b94b1ac499a2bf9294ad2f06829 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 26 Jun 2024 15:58:15 +0100 Subject: [PATCH 74/75] Merging from develop --- poetry.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5ccbccff1e7..74dc0fe18ec 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1520,13 +1520,13 @@ files = [ [[package]] name = "jsii" -version = "1.100.0" +version = "1.101.0" description = "Python client for jsii runtime" optional = false python-versions = "~=3.8" files = [ - {file = "jsii-1.100.0-py3-none-any.whl", hash = "sha256:3564188028b5b2ddf7fbd50bb28555125e684a6c4540f591c9be088bc073fdb8"}, - {file = "jsii-1.100.0.tar.gz", hash = "sha256:b267f651086cbdbb2ba336099361ebfc109dbe86f3c8671636338090c4b4d2f4"}, + {file = "jsii-1.101.0-py3-none-any.whl", hash = "sha256:b78b87f8316560040ad0b9dca1682d73b6532a33acf4ecf56185d1ae5edb54fa"}, + {file = "jsii-1.101.0.tar.gz", hash = "sha256:043c4d3d0d09af3c7265747f4da9c95770232477f75c846640df4c63d01b19cb"}, ] [package.dependencies] @@ -2759,13 +2759,13 @@ toml = ["tomli (>=2.0.1)"] [[package]] name = "redis" -version = "5.0.6" +version = "5.0.7" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"}, - {file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"}, + {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, + {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, ] [package.dependencies] @@ -3084,13 +3084,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "sentry-sdk" -version = "2.6.0" +version = "2.7.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" files = [ - {file = "sentry_sdk-2.6.0-py2.py3-none-any.whl", hash = "sha256:422b91cb49378b97e7e8d0e8d5a1069df23689d45262b86f54988a7db264e874"}, - {file = "sentry_sdk-2.6.0.tar.gz", hash = "sha256:65cc07e9c6995c5e316109f138570b32da3bd7ff8d0d0ee4aaf2628c3dd8127d"}, + {file = "sentry_sdk-2.7.0-py2.py3-none-any.whl", hash = "sha256:db9594c27a4d21c1ebad09908b1f0dc808ef65c2b89c1c8e7e455143262e37c1"}, + {file = "sentry_sdk-2.7.0.tar.gz", hash = "sha256:d846a211d4a0378b289ced3c434480945f110d0ede00450ba631fc2852e7a0d4"}, ] [package.dependencies] @@ -3120,7 +3120,7 @@ langchain = ["langchain (>=0.0.210)"] loguru = ["loguru (>=0.5)"] openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -3716,4 +3716,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0.0" -content-hash = "b51afbf69445f2f45a3a3e0134cdb3c7f5f654468d633d10d4fc356612fd94a4" +content-hash = "25868c659cf950a66656c81b83c7ff3dfd1b21fb221e2f1cbd57b058323ae114" From a612e38b4d54be56d24ac6067a08e8082504ad6e Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 26 Jun 2024 16:10:22 +0100 Subject: [PATCH 75/75] Tests --- .../required_dependencies/test_appsync_resolver_event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py b/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py index a1a010c251a..ace6032e131 100644 --- a/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py +++ b/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py @@ -80,7 +80,7 @@ def test_appsync_resolver_direct(): raw_event = load_event("appSyncDirectResolver.json") parsed_event = AppSyncResolverEvent(raw_event) - assert parsed_event.source is None + assert parsed_event.source == {} assert parsed_event.arguments.get("id") == raw_event["arguments"]["id"] assert parsed_event.stash == {} assert parsed_event.prev_result is None @@ -112,7 +112,7 @@ def test_appsync_resolver_event_info(): event = AppSyncResolverEvent(event) - assert event.source is None + assert event.source == {} assert event.identity is None assert event.info is not None assert isinstance(event.info, AppSyncResolverEventInfo)