Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/graphql/execution/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,14 @@ def assume_not_awaitable(_value: Any) -> TypeGuard[Awaitable]:
return False


def assume_not_async_iterable(_value: Any) -> TypeGuard[AsyncIterable]:
"""Replacement for is_async_iterable if everything is assumed to be synchronous.

:meta private:
"""
return False


def execute_sync(
schema: GraphQLSchema,
document: DocumentNode,
Expand Down Expand Up @@ -549,6 +557,7 @@ def execute_sync(
if callable(check_sync)
else (None if check_sync else assume_not_awaitable)
)
is_async_iterable = assume_not_async_iterable if not check_sync else None

result = experimental_execute_incrementally(
schema,
Expand All @@ -565,6 +574,7 @@ def execute_sync(
middleware,
executor_class,
is_awaitable,
is_async_iterable,
hide_suggestions=hide_suggestions,
abort_signal=abort_signal,
hooks=hooks,
Expand Down
72 changes: 71 additions & 1 deletion tests/execution/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
from graphql.execution import execute, execute_sync
from graphql.language import parse
from graphql.pyutils import is_awaitable
from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString
from graphql.type import (
GraphQLField,
GraphQLList,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
)
from graphql.validation import validate

from ..fixtures import cleanup
Expand Down Expand Up @@ -36,6 +42,29 @@ async def _resolve_async(root_value, _info):
),
)

list_schema = GraphQLSchema(
GraphQLObjectType(
"Query",
{
"listField": GraphQLField(
GraphQLList(GraphQLString), resolve=_resolve_sync
)
},
)
)

class _SyncAndAsyncIterable:
"""An iterable that is also an async iterable, like a Django QuerySet."""

def __iter__(self):
return iter(["sync"])

def __aiter__(self):
return self

async def __anext__(self): # pragma: no cover
raise StopAsyncIteration

def does_not_return_an_awaitable_for_initial_errors():
doc = "fragment Example on Query { syncField }"
assert execute(schema, parse(doc), "rootValue") == (
Expand Down Expand Up @@ -156,6 +185,47 @@ async def throws_if_encountering_async_iterable_execution_without_check_sync():
del exc_info
cleanup()

def iterates_sync_and_async_iterables_synchronously_without_check_sync():
doc = "query Example { listField }"
result = execute_sync(
list_schema, document=parse(doc), root_value=_SyncAndAsyncIterable()
)
assert result == ({"listField": ["sync"]}, None)

async def throws_if_encountering_sync_and_async_iterable_with_check_sync():
doc = "query Example { listField }"
with pytest.raises(RuntimeError) as exc_info:
execute_sync(
list_schema,
document=parse(doc),
root_value=_SyncAndAsyncIterable(),
check_sync=True,
)
msg = str(exc_info.value)
assert msg == "GraphQL execution failed to complete synchronously."
del exc_info
cleanup()

def reports_async_iterables_as_errors_without_check_sync():
async def _async_iterable():
yield "async" # pragma: no cover

doc = "query Example { listField }"
result = execute_sync(
list_schema, document=parse(doc), root_value=_async_iterable()
)
assert result == (
{"listField": None},
[
{
"message": "Expected Iterable, but did not find one"
" for field 'Query.listField'.",
"locations": [(1, 17)],
"path": ["listField"],
}
],
)

def describe_graphql_sync():
def reports_errors_raised_during_schema_validation():
bad_schema = GraphQLSchema()
Expand Down