From 98baba0f0365c9ee1be030cb3db3ad31d94ee991 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 7 Apr 2021 17:38:01 +0200 Subject: [PATCH 1/8] docs: adjust navigation to allow future handlers Signed-off-by: heitorlessa --- docs/core/{event_handler.md => event_handler/appsync.md} | 4 ++-- mkdocs.yml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) rename docs/core/{event_handler.md => event_handler/appsync.md} (99%) diff --git a/docs/core/event_handler.md b/docs/core/event_handler/appsync.md similarity index 99% rename from docs/core/event_handler.md rename to docs/core/event_handler/appsync.md index 2f7948fcc25..323a8c330f3 100644 --- a/docs/core/event_handler.md +++ b/docs/core/event_handler/appsync.md @@ -1,6 +1,6 @@ --- -title: Event Handler -description: Utility +title: Appsync +description: Core utility --- Event handler decorators for common Lambda events diff --git a/mkdocs.yml b/mkdocs.yml index 67046f11147..7de40f10c13 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,7 +12,8 @@ nav: - core/tracer.md - core/logger.md - core/metrics.md - - core/event_handler.md + - Event Handler: + - core/event_handler/appsync.md - Utilities: - utilities/middleware_factory.md - utilities/parameters.md From 8a2cbf58af84d08c6a8cd4a765bd5a8f4cc73594 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 7 Apr 2021 17:38:20 +0200 Subject: [PATCH 2/8] chore: add back to top button feature Signed-off-by: heitorlessa --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 7de40f10c13..3f42dd136bf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,6 +31,7 @@ theme: features: - navigation.sections - navigation.expand + - navigation.top icon: repo: fontawesome/brands/github logo: media/aws-logo-light.svg From 40c38c191a1a0aab5cfa79eec28255f623bb76bc Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 8 Apr 2021 12:27:06 +0200 Subject: [PATCH 3/8] chore: add highlight for inline block Signed-off-by: heitorlessa --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 3f42dd136bf..43a7e125696 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,6 +54,7 @@ markdown_extensions: toc_depth: 4 - attr_list - pymdownx.emoji + - pymdownx.inlinehilite copyright: Copyright © 2021 Amazon Web Services From 5ec25239a62e2ac447b0b2a158df307d6878ed48 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 8 Apr 2021 12:27:34 +0200 Subject: [PATCH 4/8] docs: add initial structure Signed-off-by: heitorlessa --- docs/core/event_handler/appsync.md | 197 +++++++++++++++++++++++++++-- 1 file changed, 184 insertions(+), 13 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 323a8c330f3..cee8ee83c52 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -3,26 +3,197 @@ title: Appsync description: Core utility --- -Event handler decorators for common Lambda events +Event handler for AWS AppSync Direct Lambda Resolver and Amplify GraphQL Transformer +### 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) to access resolver and identity information + +## Terminology + +* **[Direct Lambda Resolver](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html)**. 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)**. 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. + +## Getting started + +### 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. + +This is the sample infrastructure we are using for the initial examples with a AppSync Direct Lambda Resolver. + +=== "schema.graphql" + + !!! 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) for understanding how to define types, nesting, and pagination. + + ```gql + schema { + query:Query + } -## AppSync Resolver Decorator + type Query { + getTodo(id: ID!): Todo + listTodos: [Todo] + } + + type Todo { + id: ID! + title: String + description: String + done: Boolean + } + ``` + +=== "template.yml" + + ```yaml hl_lines="37-42 50-55 61-62 78-91 96-120" + AWSTemplateFormatVersion: '2010-09-09' + Transform: AWS::Serverless-2016-10-31 + Description: Hello world Direct Lambda Resolver + + Globals: + Function: + Timeout: 5 + Runtime: python3.8 + Tracing: Active + Environment: + Variables: + # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/latest/#environment-variables + LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_SERVICE_NAME: sample_resolver + + Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.lambda_handler + CodeUri: hello_world + Description: Sample Lambda Powertools Direct Lambda Resolver + Tags: + SOLUTION: LambdaPowertoolsPython + + # IAM Permissions and Roles + + AppSyncServiceRole: + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Principal: + Service: + - "appsync.amazonaws.com" + Action: + - "sts:AssumeRole" + + InvokeLambdaResolverPolicy: + Type: "AWS::IAM::Policy" + Properties: + PolicyName: "DirectAppSyncLambda" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Action: "lambda:invokeFunction" + Resource: + - !GetAtt HelloWorldFunction.Arn + Roles: + - !Ref AppSyncServiceRole + + # GraphQL API + + HelloWorldApi: + Type: "AWS::AppSync::GraphQLApi" + Properties: + Name: HelloWorldApi + AuthenticationType: "API_KEY" + XrayEnabled: true + + HelloWorldApiKey: + Type: AWS::AppSync::ApiKey + Properties: + ApiId: !GetAtt HelloWorldApi.ApiId + + HelloWorldApiSchema: + Type: "AWS::AppSync::GraphQLSchema" + Properties: + ApiId: !GetAtt HelloWorldApi.ApiId + Definition: | + schema { + query:Query + } + + type Query { + getTodo(id: ID!): Todo + listTodos: [Todo] + } + + type Todo { + id: ID! + title: String + description: String + done: Boolean + } + + # Lambda Direct Data Source and Resolver + + HelloWorldFunctionDataSource: + Type: "AWS::AppSync::DataSource" + Properties: + ApiId: !GetAtt HelloWorldApi.ApiId + Name: "HelloWorldLambdaDirectResolver" + Type: "AWS_LAMBDA" + ServiceRoleArn: !GetAtt AppSyncServiceRole.Arn + LambdaConfig: + LambdaFunctionArn: !GetAtt HelloWorldFunction.Arn + + ListTodosResolver: + Type: "AWS::AppSync::Resolver" + Properties: + ApiId: !GetAtt HelloWorldApi.ApiId + TypeName: "Query" + FieldName: "listTodos" + DataSourceName: !GetAtt HelloWorldFunctionDataSource.Name + + GetTodoResolver: + Type: "AWS::AppSync::Resolver" + Properties: + ApiId: !GetAtt HelloWorldApi.ApiId + TypeName: "Query" + FieldName: "getTodo" + DataSourceName: !GetAtt HelloWorldFunctionDataSource.Name + + + Outputs: + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + + HelloWorldAPI: + Value: !GetAtt HelloWorldApi.Arn + ``` -> New in 1.14.0 -AppSync resolver decorator is a concise way to create lambda functions to handle AppSync resolvers for multiple -`typeName` and `fieldName` declarations. This decorator builds on top of the -[AppSync Resolver ](/utilities/data_classes#appsync-resolver) data class and therefore works with [Amplify GraphQL Transform Library](https://docs.amplify.aws/cli/graphql-transformer/function){target="_blank"} (`@function`), +### Resolver decorator + +AppSync resolver decorator is a concise way to create lambda functions to handle AppSync resolvers for multiple `typeName` and `fieldName` declarations. This decorator builds on top of the [AppSync Resolver ](/utilities/data_classes#appsync-resolver) data class and therefore works with [Amplify GraphQL Transform Library](https://docs.amplify.aws/cli/graphql-transformer/function){target="_blank"} (`@function`), and [AppSync Direct Lambda Resolvers](https://aws.amazon.com/blogs/mobile/appsync-direct-lambda/){target="_blank"} -### Key Features -* Works with any of the existing Powertools utilities by allow you to create your own `lambda_handler` function -* Supports an implicit handler where in `app = AppSyncResolver()` can be invoked directly as `app(event, context)` -* `resolver` decorator has flexible or strict matching against `fieldName` -* Arguments are automatically passed into your function -* AppSyncResolver includes `current_event` and `lambda_cotext` fields can be used to pass in the original `AppSyncResolver` or `LambdaContext` - objects +## Advanced + + + ### Amplify GraphQL Example From 1a8f9da663d8170613b3affb5efb35148d69f0d1 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 8 Apr 2021 15:00:16 +0200 Subject: [PATCH 5/8] docs(appsync_handler): polish advanced section --- docs/core/event_handler/appsync.md | 278 +++++++++++++++------ docs/shared/getting_started_schema.graphql | 15 ++ 2 files changed, 217 insertions(+), 76 deletions(-) create mode 100644 docs/shared/getting_started_schema.graphql diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index cee8ee83c52..d3593b6b5fe 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -11,12 +11,14 @@ Event handler for AWS AppSync Direct Lambda Resolver and Amplify GraphQL Transfo * 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) to access resolver and identity information +* 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 ## Terminology -* **[Direct Lambda Resolver](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html)**. 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)**. 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. +**[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. ## Getting started @@ -31,22 +33,8 @@ This is the sample infrastructure we are using for the initial examples with a A !!! 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) for understanding how to define types, nesting, and pagination. - ```gql - schema { - query:Query - } - - type Query { - getTodo(id: ID!): Todo - listTodos: [Todo] - } - - type Todo { - id: ID! - title: String - description: String - done: Boolean - } + ```typescript + --8<-- "docs/shared/getting_started_schema.graphql" ``` === "template.yml" @@ -186,30 +174,185 @@ This is the sample infrastructure we are using for the initial examples with a A ### Resolver decorator -AppSync resolver decorator is a concise way to create lambda functions to handle AppSync resolvers for multiple `typeName` and `fieldName` declarations. This decorator builds on top of the [AppSync Resolver ](/utilities/data_classes#appsync-resolver) data class and therefore works with [Amplify GraphQL Transform Library](https://docs.amplify.aws/cli/graphql-transformer/function){target="_blank"} (`@function`), -and [AppSync Direct Lambda Resolvers](https://aws.amazon.com/blogs/mobile/appsync-direct-lambda/){target="_blank"} +You can define your functions to match GraphQL types and fields with the `app.resolver()` decorator. +Here's an example where we have two separate functions to resolve `getTodo` and `listTodos` fields within the `Query` type. For completion, we use Scalar type utilities to generate the right output based on our schema definition. + +!!! note "All type and field cases will be converted to snake_case to match Python idioms" + For example, `getTodo` field will match a function named `get_todo`. + +=== "app.py" + + ```python hl_lines="3-4 8 30-31 38-39 46" + from aws_lambda_powertools import Logger, Tracer + + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes.appsync import scalar_types_utils + + tracer = Tracer(service="sample_resolver") + logger = Logger(service="sample_resolver") + app = AppSyncResolver() + + # Note that `creation_time` isn't available in the schema + # This utility also takes into account what info you make available at API level vs what's stored + TODOS = [ + { + "id": scalar_types_utils.make_id(), # type ID or String + "title": "First task", + "description": "String", + "done": False, + "creation_time": scalar_types_utils.aws_datetime(), # type AWSDateTime + }, + { + "id": scalar_types_utils.make_id(), + "title": "Second task", + "description": "String", + "done": True, + "creation_time": scalar_types_utils.aws_datetime(), + }, + ] -## Advanced + @app.resolver(type_name="Query", field_name="getTodo") + def get_todo(id: str = ""): + logger.info(f"Fetching Todo {id}") + todo = [todo for todo in TODOS if todo["id"] == id] + + return todo + + + @app.resolver(type_name="Query", field_name="listTodos") + def list_todos(): + return TODOS + + + @logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + +=== "schema.graphql" + + ```typescript + --8<-- "docs/shared/getting_started_schema.graphql" + ``` + +=== "getTodo_event.json" + ```json + { + "arguments": { + "id": "7e362732-c8cd-4405-b090-144ac9b38960" + }, + "identity": null, + "source": null, + "request": { + "headers": { + "x-forwarded-for": "1.2.3.4, 5.6.7.8", + "accept-encoding": "gzip, deflate, br", + "cloudfront-viewer-country": "NL", + "cloudfront-is-tablet-viewer": "false", + "referer": "https://eu-west-1.console.aws.amazon.com/appsync/home?region=eu-west-1", + "via": "2.0 9fce949f3749407c8e6a75087e168b47.cloudfront.net (CloudFront)", + "cloudfront-forwarded-proto": "https", + "origin": "https://eu-west-1.console.aws.amazon.com", + "x-api-key": "da1-c33ullkbkze3jg5hf5ddgcs4fq", + "content-type": "application/json", + "x-amzn-trace-id": "Root=1-606eb2f2-1babc433453a332c43fb4494", + "x-amz-cf-id": "SJw16ZOPuMZMINx5Xcxa9pB84oMPSGCzNOfrbJLvd80sPa0waCXzYQ==", + "content-length": "114", + "x-amz-user-agent": "AWS-Console-AppSync/", + "x-forwarded-proto": "https", + "host": "ldcvmkdnd5az3lm3gnf5ixvcyy.appsync-api.eu-west-1.amazonaws.com", + "accept-language": "en-US,en;q=0.5", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:78.0) Gecko/20100101 Firefox/78.0", + "cloudfront-is-desktop-viewer": "true", + "cloudfront-is-mobile-viewer": "false", + "accept": "*/*", + "x-forwarded-port": "443", + "cloudfront-is-smarttv-viewer": "false" + } + }, + "prev": null, + "info": { + "parentTypeName": "Query", + "selectionSetList": [ + "title", + "id" + ], + "selectionSetGraphQL": "{\n title\n id\n}", + "fieldName": "getTodo", + "variables": {} + }, + "stash": {} + } + ``` +=== "listTodos_event.json" + ```json + { + "arguments": {}, + "identity": null, + "source": null, + "request": { + "headers": { + "x-forwarded-for": "1.2.3.4, 5.6.7.8", + "accept-encoding": "gzip, deflate, br", + "cloudfront-viewer-country": "NL", + "cloudfront-is-tablet-viewer": "false", + "referer": "https://eu-west-1.console.aws.amazon.com/appsync/home?region=eu-west-1", + "via": "2.0 9fce949f3749407c8e6a75087e168b47.cloudfront.net (CloudFront)", + "cloudfront-forwarded-proto": "https", + "origin": "https://eu-west-1.console.aws.amazon.com", + "x-api-key": "da1-c33ullkbkze3jg5hf5ddgcs4fq", + "content-type": "application/json", + "x-amzn-trace-id": "Root=1-606eb2f2-1babc433453a332c43fb4494", + "x-amz-cf-id": "SJw16ZOPuMZMINx5Xcxa9pB84oMPSGCzNOfrbJLvd80sPa0waCXzYQ==", + "content-length": "114", + "x-amz-user-agent": "AWS-Console-AppSync/", + "x-forwarded-proto": "https", + "host": "ldcvmkdnd5az3lm3gnf5ixvcyy.appsync-api.eu-west-1.amazonaws.com", + "accept-language": "en-US,en;q=0.5", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:78.0) Gecko/20100101 Firefox/78.0", + "cloudfront-is-desktop-viewer": "true", + "cloudfront-is-mobile-viewer": "false", + "accept": "*/*", + "x-forwarded-port": "443", + "cloudfront-is-smarttv-viewer": "false" + } + }, + "prev": null, + "info": { + "parentTypeName": "Query", + "selectionSetList": [ + "id", + "title" + ], + "selectionSetGraphQL": "{\n id\n title\n}", + "fieldName": "listTodos", + "variables": {} + }, + "stash": {} + } + ``` +## Advanced +### Amplify GraphQL Transformer -### Amplify GraphQL Example +Assuming you have [Amplify CLI installed](https://docs.amplify.aws/cli/start/install){target="_blank"}, create a new API using `amplify add api` and use the following GraphQL Schema. -Create a new GraphQL api via `amplify add api` and add the following to the new `schema.graphql` + === "schema.graphql" - ```typescript hl_lines="7-10 17-18 22-25" + ```typescript hl_lines="8 10 18 23 25" @model type Merchant { id: String! name: String! description: String - # Resolves to `get_extra_info` - extraInfo: ExtraInfo @function(name: "merchantInfo-${env}") # Resolves to `common_field` commonField: String @function(name: "merchantInfo-${env}") } @@ -230,33 +373,36 @@ Create a new GraphQL api via `amplify add api` and add the following to the new } ``` -Create two new simple Python functions via `amplify add function` and run `pipenv install aws-lambda-powertools` to -add Powertools as a dependency. Add the following example lambda implementation +[Create two new basic Python functions](https://docs.amplify.aws/cli/function#set-up-a-function){target="_blank"} via `amplify add function`. + +!!! note "Amplify CLI generated functions use `Pipenv` as a dependency manager" + Your function source code is located at **`amplify/backend/function/your-function-name`**. + +Within your function's folder, add Lambda Powertools as a dependency with `pipenv install aws-lambda-powertools`. + +Use the following code for `merchantInfo` and `searchMerchant` functions respectively. === "merchantInfo/src/app.py" - ```python hl_lines="1-2 6 8-9 13-14 18-19 24 26" + ```python hl_lines="4-5 9 11-12 15-16 23" + from aws_lambda_powertools import Logger, Tracer + + from aws_lambda_powertools.logging import correlation_paths from aws_lambda_powertools.event_handler import AppSyncResolver - from aws_lambda_powertools.logging import Logger, Tracer, correlation_paths + from aws_lambda_powertools.utilities.data_classes.appsync import scalar_types_utils - tracer = Tracer() - logger = Logger() + tracer = Tracer(service="sample_graphql_transformer_resolver") + logger = Logger(service="sample_graphql_transformer_resolver") app = AppSyncResolver() @app.resolver(type_name="Query", field_name="listLocations") def list_locations(page: int = 0, size: int = 10): - # Your logic to fetch locations - ... - - @app.resolver(type_name="Merchant", field_name="extraInfo") - def get_extra_info(): - # Can use `app.current_event.source["id"]` to filter within the Merchant context - ... + return [{"id": 100, "name": "Smooth Grooves"}] @app.resolver(field_name="commonField") def common_field(): # Would match all fieldNames matching 'commonField' - ... + return scalar_types_utils.make_id() @tracer.capture_lambda_handler @logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER) @@ -265,18 +411,29 @@ add Powertools as a dependency. Add the following example lambda implementation ``` === "searchMerchant/src/app.py" - ```python hl_lines="1 3 5-6" + ```python hl_lines="1 4 6-7" from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.data_classes.appsync import scalar_types_utils app = AppSyncResolver() @app.resolver(type_name="Query", field_name="findMerchant") def find_merchant(search: str): - # Your special search function - ... + return [ + { + "id": scalar_types_utils.make_id(), + "name": "Brewer Brewing", + "description": "Mike Brewer's IPA brewing place" + }, + { + "id": scalar_types_utils.make_id(), + "name": "Serverlessa's Bakery", + "description": "Lessa's sourdough place" + }, + ] ``` -Example AppSync resolver events +**Example AppSync GraphQL Transformer Function resolver events** === "Query.listLocations event" @@ -307,37 +464,6 @@ Example AppSync resolver events } ``` -=== "Merchant.extraInfo event" - - ```json hl_lines="2-5 14-17" - { - "typeName": "Merchant", - "fieldName": "extraInfo", - "arguments": { - }, - "identity": { - "claims": { - "iat": 1615366261 - ... - }, - "username": "mike", - ... - }, - "source": { - "id": "12345", - "name: "Pizza Parlor" - }, - "request": { - "headers": { - "x-amzn-trace-id": "Root=1-60488877-0b0c4e6727ab2a1c545babd0", - "x-forwarded-for": "127.0.0.1" - ... - } - }, - ... - } - ``` - === "*.commonField event" ```json hl_lines="2 3" diff --git a/docs/shared/getting_started_schema.graphql b/docs/shared/getting_started_schema.graphql new file mode 100644 index 00000000000..fd82d45e6f0 --- /dev/null +++ b/docs/shared/getting_started_schema.graphql @@ -0,0 +1,15 @@ +schema { + query: Query +} + +type Query { + getTodo(id: ID!): Todo # will match a `def get_todo(id: str)` function + listTodos: [Todo] # will match a `def list_todos()` function +} + +type Todo { + id: ID! + title: String + description: String + done: Boolean +} From e03aabe1355b4e04b086293ed5e3f5b3e06efd77 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 8 Apr 2021 15:31:51 +0200 Subject: [PATCH 6/8] docs(appsync_handler): add nested mappings & async --- docs/core/event_handler/appsync.md | 90 ++++++++++++++++++++-- docs/shared/getting_started_schema.graphql | 4 +- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index d3593b6b5fe..9b2d5e99542 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -178,9 +178,6 @@ You can define your functions to match GraphQL types and fields with the `app.re Here's an example where we have two separate functions to resolve `getTodo` and `listTodos` fields within the `Query` type. For completion, we use Scalar type utilities to generate the right output based on our schema definition. -!!! note "All type and field cases will be converted to snake_case to match Python idioms" - For example, `getTodo` field will match a function named `get_todo`. - === "app.py" ```python hl_lines="3-4 8 30-31 38-39 46" @@ -336,8 +333,90 @@ Here's an example where we have two separate functions to resolve `getTodo` and "stash": {} } ``` + ## Advanced +### Nested mappings + +You can nest `app.resolver()` decorator multiple times when resolving fields with the same return. + +=== "nested_mappings.py" + + ```python hl_lines="4 8 10-12 18" + from aws_lambda_powertools import Logger, Tracer + + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler import AppSyncResolver + + tracer = Tracer(service="sample_resolver") + logger = Logger(service="sample_resolver") + app = AppSyncResolver() + + @app.resolver(field_name="listLocations") + @app.resolver(field_name="locations") + def get_locations(name: str, description: str = ""): + return name + description + + @logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + +=== "schema.graphql" + + ```typescript hl_lines="6 20" + schema { + query: Query + } + + type Query { + listLocations: [Todo] + } + + type Location { + id: ID! + name: String! + description: String + address: String + } + + type Merchant { + id: String! + name: String! + description: String + locations: [Location] + } + ``` + +### Async functions + +For Lambda Python3.8+ runtime, this utility supports async functions when you use in conjunction with `asyncio.run`. + +=== "async_resolver.py" + ```python hl_lines="4 8 10-12 20" + from aws_lambda_powertools import Logger, Tracer + + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler import AppSyncResolver + + tracer = Tracer(service="sample_resolver") + logger = Logger(service="sample_resolver") + app = AppSyncResolver() + + @app.resolver(type_name="Query", field_name="listTodos") + async def list_todos(): + todos = await some_async_io_call() + return todos + + @logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + result = app.resolve(event, context) + + return asyncio.run(result) + ``` + ### Amplify GraphQL Transformer Assuming you have [Amplify CLI installed](https://docs.amplify.aws/cli/start/install){target="_blank"}, create a new API using `amplify add api` and use the following GraphQL Schema. @@ -348,8 +427,7 @@ Assuming you have [Amplify CLI installed](https://docs.amplify.aws/cli/start/ins ```typescript hl_lines="8 10 18 23 25" @model - type Merchant - { + type Merchant { id: String! name: String! description: String @@ -360,7 +438,7 @@ Assuming you have [Amplify CLI installed](https://docs.amplify.aws/cli/start/ins type Location { id: ID! name: String! - address: Address + address: String # Resolves to `common_field` commonField: String @function(name: "merchantInfo-${env}") } diff --git a/docs/shared/getting_started_schema.graphql b/docs/shared/getting_started_schema.graphql index fd82d45e6f0..c738156bd73 100644 --- a/docs/shared/getting_started_schema.graphql +++ b/docs/shared/getting_started_schema.graphql @@ -3,8 +3,8 @@ schema { } type Query { - getTodo(id: ID!): Todo # will match a `def get_todo(id: str)` function - listTodos: [Todo] # will match a `def list_todos()` function + getTodo(id: ID!): Todo + listTodos: [Todo] } type Todo { From 98f22f33092d4686c3ed1312575ead6c97d5cb3c Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 8 Apr 2021 15:37:56 +0200 Subject: [PATCH 7/8] docs(appsync_handler): add last touches before review --- docs/core/event_handler/appsync.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 9b2d5e99542..88a3808fd8b 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -3,7 +3,7 @@ title: Appsync description: Core utility --- -Event handler for AWS AppSync Direct Lambda Resolver and Amplify GraphQL Transformer +Event handler for AWS AppSync Direct Lambda Resolver and Amplify GraphQL Transformer. ### Key Features @@ -13,6 +13,7 @@ Event handler for AWS AppSync Direct Lambda Resolver and Amplify GraphQL Transfo * 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 ## Terminology @@ -30,7 +31,7 @@ This is the sample infrastructure we are using for the initial examples with a A === "schema.graphql" - !!! tip "Designing GraphQL Schemas for the first time" + !!! 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) for understanding how to define types, nesting, and pagination. ```typescript @@ -178,6 +179,8 @@ You can define your functions to match GraphQL types and fields with the `app.re Here's an example where we have two separate functions to resolve `getTodo` and `listTodos` fields within the `Query` type. For completion, we use Scalar type utilities to generate the right output based on our schema definition. +!!! info "GraphQL arguments are passed as function arguments" + === "app.py" ```python hl_lines="3-4 8 30-31 38-39 46" From 16e650ba3fafd4c62c4f9448133fb5d7cc8147a7 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Thu, 8 Apr 2021 16:47:14 +0200 Subject: [PATCH 8/8] docs(appsync_handler): add ltesting section --- docs/core/event_handler/appsync.md | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 88a3808fd8b..00881e4e868 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -599,3 +599,36 @@ Use the following code for `merchantInfo` and `searchMerchant` functions respect ... } ``` + +## Testing your code + +You can test your resolvers by passing a mocked or actual AppSync Lambda event that you're expecting. + +You can use either `app.resolve(event, context)` or simply `app(event, context)`. + +Here's an example from our internal functional test. + +=== "test_direct_resolver.py" + ```python + def test_direct_resolver(): + # Check whether we can handle an example appsync direct resolver + # load_event primarily deserialize the JSON event into a dict + mock_event = load_event("appSyncDirectResolver.json") + + app = AppSyncResolver() + + @app.resolver(field_name="createSomething") + def create_something(id: str): + assert app.lambda_context == {} + return id + + # Call the implicit handler + result = app(mock_event, {}) + + assert result == "my identifier" + ``` + +=== "appSyncDirectResolver.json" + ```json + --8<-- "tests/events/appSyncDirectResolver.json" + ```