graphql-python / graphql-core-legacy

GraphQL base implementation for Python (legacy version – see graphql-core for the current one)
MIT License
374 stars 183 forks source link

add support for `yield from graphql_async(schema, query)` #254

Closed voith closed 5 years ago

voith commented 5 years ago

What was wrong?

How was it fixed?

voith commented 5 years ago

cc @syrusakbary

voith commented 5 years ago

tests are failing because yield from syntax is not supported on python2.7 and pypy

voith commented 5 years ago

The inspiration for this feature came from here. I will try to explain the problem again over here. Here's an MWE to explain the problem:

import asyncio

from graphql import graphql
from graphql.execution.executors.asyncio import AsyncioExecutor
from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString

async def resolver(context, *_):
    await asyncio.sleep(0.001)
    return "hey"

async def resolver_2(context, *_):
    await asyncio.sleep(0.003)
    return "hey2"

def resolver_3(contest, *_):
    return "hey3"

Type = GraphQLObjectType(
        "Type",
        {
            "a": GraphQLField(GraphQLString, resolver=resolver),
            "b": GraphQLField(GraphQLString, resolver=resolver_2),
            "c": GraphQLField(GraphQLString, resolver=resolver_3),
        },
    )

async def get_result(query):
    # AsyncioExecutor will try to execute `loop.run_until_complete`
    # But since trinity is already running `run_forever`, 
    # it will result in a error: `RuntimeError('This event loop is already running',)`
    result = graphql(GraphQLSchema(Type), query, executor=AsyncioExecutor())
    return result

if __name__ == "__main__":
    # think of this loop used by an async framework event loop
    loop = asyncio.get_event_loop()
    query = "{a, b, c}"
    # The async will have its own event_loop running
    result = loop.run_until_complete(get_result(query))
    print('result', result.data)
    print('error', result.errors)

This gives the following output:

result None
error [RuntimeError('Cannot run the event loop while another loop is running',)]

If I try to use a new_loop for AsyncioExecutor, It results in the following: In order to do so, just modify the code and replace get_result with:

async def get_result(query):
    result = graphql(GraphQLSchema(Type), query, executor=AsyncioExecutor(loop=asyncio.new_event_loop()))
    return result

The output of this code is:

result None
error [RuntimeError('Cannot run the event loop while another loop is running',)]

So the only fix I could think of was supporting await graphql_async(schema, query, executor=AsyncioExecutor()). This was done by introducing a new coroutine wait_until_finished_async on AsyncioExecutor that waits for tasks to finish asynchronously.

voith commented 5 years ago

looks like this PR is not needed. I didn't know about return_promise.

loop = asyncio.get_event_loop()

async def do_exec():
    result = await execute(
        GraphQLSchema(Type),
        ast,
        executor=AsyncioExecutor(loop),
        return_promise=True,
    )
    assert not result.errors
    assert result.data == {"a": "hey", "b": "hey2", "c": "hey3"}

loop.run_until_complete(do_exec())