-
Notifications
You must be signed in to change notification settings - Fork 157
feat(event-handler): add single resolver functionality for AppSync GraphQL API #3999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(event-handler): add single resolver functionality for AppSync GraphQL API #3999
Conversation
I have also begun exploring the implementation of the batch resolver. I am trying the powertools-python batch resolver example. Schema: type Post {
post_id: ID!
title: String
author: String
relatedPosts: [Post]
}
type Query {
getPost(post_id: ID!): Post
}
schema {
query: Query
} from __future__ import annotations
from typing import Any, TypedDict
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
app = AppSyncResolver()
logger = Logger()
posts = {
"1": { "post_id": "1", "title": "First book", "author": "Author1", },
"2": { "post_id": "2", "title": "Second book", "author": "Author2", },
"3": { "post_id": "3", "title": "Third book", "author": "Author3", },
"4": { "post_id": "4", "title": "Fourth book", "author": "Author4", },
"5": { "post_id": "5", "title": "Fifth book", "author": "Author5", }
}
posts_related = {
"1": [posts["4"]],
"2": [posts["3"], posts["5"]],
"3": [posts["2"], posts["1"]],
"4": [posts["2"], posts["1"]],
"5": [],
}
def search_batch_posts(posts: list) -> dict[str, Any]:
return {post_id: posts_related.get(post_id) for post_id in posts}
class Post(TypedDict, total=False):
post_id: str
title: str
author: str
@app.resolver(type_name="Query", field_name="getPost")
def get_post(
post_id: str = ""
) -> Post:
return posts.get(post_id, {})
@app.batch_resolver(type_name="Query", field_name="relatedPosts")
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]
# Get unique post_ids while preserving order
unique_post_ids = list(dict.fromkeys(post_ids))
# Fetch posts in a single batch operation
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]
def lambda_handler(event, context: LambdaContext) -> dict:
return app.resolve(event, context) If I try this query query MyQuery {
getPost(post_id: "2") {
relatedPosts {
post_id
author
relatedPosts {
post_id
author
}
}
}
} There is an error: If I change the typeName to Result: {
"data": {
"getPost": {
"relatedPosts": [
{
"post_id": "3",
"author": "Author3",
"relatedPosts": [
{
"post_id": "2",
"author": "Author2"
},
{
"post_id": "1",
"author": "Author1"
}
]
},
{
"post_id": "5",
"author": "Author5",
"relatedPosts": []
}
]
}
}
} The reason is probably because parentTypeName value is Not sure what I am doing wrong here. @dreamorosi @leandrodamascena |
Hi @arnabrahman, thank you so much for the PR. I'm going to need a bit more time to review this, I was out of office yesterday and catching up with things today. I expect to provide a first review tomorrow morning. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work as usual @arnabrahman - I haven't tested this on an AppSync API (will try tomorrow) but the implementation looks good. I've only left a couple minor comments.
I'll request also @leandrodamascena's review (tomorrow is ok), since he knows more about this resolver than me.
Good idea also splitting the PR for single vs batch resolvers.
Thanks for finding a bug in the Python documentation, @arnabrahman! You always exceed our standards with these catches ❤️ ! Yeah, the valid value is Thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your patience.
I ran some tests deploying this with an AppSync API and it works well, good work here!
I have left a couple comments:
- one around making the methods that register handlers generic to improve experience
- another to create a more generic
resolver
method that can handle other cases covered by the Python implementation
Besides this, I'd also see if we can add some helper functions like these for scalar types in this PR.
We can address the batchResolver
method in a separate PR.
Thanks again!
@dreamorosi Thanks for taking the time to review this. I will look at the suggestions and get back on this. |
packages/event-handler/src/appsync-graphql/AppSyncGraphQLResolver.ts
Outdated
Show resolved
Hide resolved
packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts
Outdated
Show resolved
Hide resolved
packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts
Outdated
Show resolved
Hide resolved
packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts
Show resolved
Hide resolved
This is great work. One question I have: both the AppSync events and Bedrock agents event handlers pass the it('tool function has access to the event variable', async () => {
// Prepare
const app = new BedrockAgentFunctionResolver();
app.tool(
async (_params, options) => {
return options?.event;
},
{
name: 'event-accessor',
description: 'Accesses the event object',
}
);
const event = createEvent('event-accessor');
// Act
const result = await app.resolve(event, context);
// Assess
expect(result.response.function).toEqual('event-accessor');
expect(result.response.functionResponse.responseBody.TEXT.body).toEqual(
JSON.stringify(event)
);
}); |
packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts
Outdated
Show resolved
Hide resolved
…tions for GraphQL events
…tion with a single resolver method
…nt factories with a unified onGraphqlEventFactory in tests
…ingle onGraphqlEventFactory
…d for handling GraphQL events
…lers and update tests for event and context access
81cc278
to
0817c6b
Compare
…mproved type consistency
…proved robustness
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fantastic work, all my comments have been addressed. Really looking forward to using this feature myself!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the amazing work on this utility, I really appreciate the effort and the level of quality.
I'm aiming at writing the docs for this feature tomorrow and release this on Wednesday 🎉
PS: I left one last comment to clarify what I meant in one of the review items, but I don't think we should block the PR over it.
|
Summary
This PR will add the ability to register single resolvers for AppSync GraphQL API.
Changes
appsync-graphql
utility insideevent-handler
appsync-events
kwargs
. We must match the function signature accordingly. So this is not possible, I resolved the arguments as an object.Example usage of the utility: Followed this
Issue number: #1166
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.