-
Notifications
You must be signed in to change notification settings - Fork 72
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: flask asyncio support for dataloaders #66
base: master
Are you sure you want to change the base?
Conversation
graphql_server/flask/graphqlview.py
Outdated
execution_results, all_params = run_http_query( | ||
self.schema, | ||
request_method, | ||
data, | ||
query_data=request.args, | ||
batch_enabled=self.batch, | ||
catch=catch, | ||
# Execute options | ||
root_value=self.get_root_value(), | ||
context_value=self.get_context(), | ||
middleware=self.get_middleware(), | ||
run_sync=not self.enable_async, | ||
) |
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 should be replaced by a call to previous result_results
method, to avoid repeating code ;)
graphql_server/flask/graphqlview.py
Outdated
middleware=self.get_middleware(), | ||
) | ||
if self.enable_async: | ||
execution_results, all_params = asyncio.run(self.resolve_results_async(request_method, data, catch)) |
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.
I don't like the idea of creating the event loop inside the execution step as not every user uses asyncio
for that, other alternatives could be trio
as example.
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.
Th correct approach should be the same as sanic and aiohttp integrations does:
execution_results, all_params = run_http_query(
self.schema,
request_method,
data,
query_data=request.args,
batch_enabled=self.batch,
catch=catch,
# Execute options
run_sync=not self.enable_async,
root_value=self.get_root_value(),
context_value=self.get_context(),
middleware=self.get_middleware(),
)
exec_res = (
[
ex if ex is None or isinstance(ex, ExecutionResult) else await ex
for ex in execution_results
]
if self.enable_async
else execution_results
)
Looking deeper into Flask itself, adding async capabilities seems to be a PITA as the framework itself does not support (AFAIK) async tasks by itself. However I found this alternative that seems to resemble Flask pattern: https://gitlab.com/pgjones/quart. This is even recommended from the Pallets Team as stated on this issue: pallets/werkzeug#1322 (comment) Cheers! |
@KingDarBoja you are 100% right that flask does not use asyncio and more specifically does not support ASGI. But this PR isn't about supporting ASGI, it is about supporting async calls so dataloaders can work. ASGI is full async parallel calls craziness and that is honestly not something I am interested in or care to use. (This is my understanding of it at least). What this does is creates an event loop in the context of a single WSGI request and allows you to use async/await calls in that request. The request itself is still blocking. I had to move away from this for a bit but I am going to be resuming it again next week. I am good if it is decided against allowing the creation of the event loop. But can we open up a hook inside of the call chain where someone else can with minimal work? With the removal of Promises, the original dataloaders library no longer works, which has blown apart my application and this seemed like the better call than injecting promise resolution in the same spot. I originally went the route of adding the creation of the event loop to make it easy on people and I wanted to make it a simple override of the function if you wanted to remove it as I doubt many people are using asyncio in flask right now because it doesn't make sense in a-lot of situations. I'll play around with the code a bit more next week and clean it up some if you are good with supporting this to some extent. |
Yeah, that should be the way to go, like the Still opened #68 to add support for Quart as well as it is not so complicated to support, and keep this PR for the dataloader request. Cheers! |
@KingDarBoja I finally had the bandwidth to come back to this, its been far too long. I cleaned up and minimized the changes. There is now a single hook point you can overload if you use async somewhere else in flask and setup your own thread. But out of the box it "just works" |
Problem fixed by constructing |
I recently had to deal with this at work while upgrading graphene. Would you like some help getting this out? I can review/write code/update the docs. I copy pasted the graphql_server.flask.graphqlview module and made changes, but would like to upstream that if possible. As one user, my preference is to not start the event loop inside a library. asyncio usage is often very opinionated, bespoke, and hard to unwind. I would rather this library return a coroutine and leave the event loop to users, with some examples in the docs for starting an event loop inside the user's Flask view (with asyncio.run) to get people started. For example I had a similar problem to coelin26 where I needed to init the DataLoaders within the same event loop. I imagine this will be a common thing as DataLoaders are the reason we need to move to async. Please let me know if the maintainers are interested in some help to merge and release this update! Thank you. |
- create loaders and run async query execution when dispatching GraphQL request - obtain loaders from GraphQL context in resolvers - split product resolver due to different authz enforcement cf. graphql-python/graphql-server#66 https://lightrun.com/answers/graphql-python-graphene-consider-supporting-promise-based-dataloaders-in-v3 graphql-python/graphql-core#71
- create loaders and run async query execution when dispatching GraphQL request - obtain loaders from GraphQL context in resolvers - split product resolver due to different authz enforcement cf. graphql-python/graphql-server#66 https://lightrun.com/answers/graphql-python-graphene-consider-supporting-promise-based-dataloaders-in-v3 graphql-python/graphql-core#71
- create loaders and run async query execution when dispatching GraphQL request - obtain loaders from GraphQL context in resolvers - split product resolver due to different authz enforcement cf. graphql-python/graphql-server#66 https://lightrun.com/answers/graphql-python-graphene-consider-supporting-promise-based-dataloaders-in-v3 graphql-python/graphql-core#71
@fffergal I agree that starting an event loop inside the library is not ideal. If you're still interested in working on this and submitting a PR I'd be happy to review. |
Just opened #102 which implements this for Flask 2. |
I'm sorry, I don't work at the same place any more so I'm not using graphql-python at the moment. I did notice while using asyncio DataLoaders, queries weren't being grouped together anymore, so there may be more to getting asyncio DataLoaders working effectively, vs. just working. |
Resolves #65
This takes the approach that no one should already have an event loop and makes one and runs it in context. This is also only python 3.7+ supported because of the use of
asyncio.run
. I am not sure if the run logic should be backported into here so it works on 3.6 as well.