Skip to content

Commit a65ca17

Browse files
committed
Added 'execute_sync': synchronous version of execute.
Replicates graphql/graphql-js@8f3d09b
1 parent 44e9b3f commit a65ca17

15 files changed

+281
-173
lines changed

Diff for: src/graphql/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@
262262
# Execute GraphQL documents.
263263
from .execution import (
264264
execute,
265+
execute_sync,
265266
default_field_resolver,
266267
default_type_resolver,
267268
get_directive_values,
@@ -611,6 +612,7 @@
611612
"EnumTypeExtensionNode",
612613
"InputObjectTypeExtensionNode",
613614
"execute",
615+
"execute_sync",
614616
"default_field_resolver",
615617
"default_type_resolver",
616618
"get_directive_values",

Diff for: src/graphql/execution/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .execute import (
88
execute,
9+
execute_sync,
910
default_field_resolver,
1011
default_type_resolver,
1112
ExecutionContext,
@@ -19,6 +20,7 @@
1920

2021
__all__ = [
2122
"execute",
23+
"execute_sync",
2224
"default_field_resolver",
2325
"default_type_resolver",
2426
"ExecutionContext",

Diff for: src/graphql/execution/execute.py

+58-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from asyncio import gather
1+
from asyncio import ensure_future, gather
22
from collections.abc import Mapping
3+
from inspect import isawaitable
34
from typing import (
45
Any,
56
Awaitable,
@@ -68,6 +69,7 @@
6869
"default_field_resolver",
6970
"default_type_resolver",
7071
"execute",
72+
"execute_sync",
7173
"get_field_def",
7274
"ExecutionResult",
7375
"ExecutionContext",
@@ -1094,6 +1096,61 @@ def execute(
10941096
return exe_context.build_response(data)
10951097

10961098

1099+
def assume_not_awaitable(_value: Any) -> bool:
1100+
"""Replacement for isawaitable if everything is assumed to be synchronous."""
1101+
return False
1102+
1103+
1104+
def execute_sync(
1105+
schema: GraphQLSchema,
1106+
document: DocumentNode,
1107+
root_value: Any = None,
1108+
context_value: Any = None,
1109+
variable_values: Optional[Dict[str, Any]] = None,
1110+
operation_name: Optional[str] = None,
1111+
field_resolver: Optional[GraphQLFieldResolver] = None,
1112+
type_resolver: Optional[GraphQLTypeResolver] = None,
1113+
middleware: Optional[Middleware] = None,
1114+
execution_context_class: Optional[Type["ExecutionContext"]] = None,
1115+
check_sync: bool = False,
1116+
) -> ExecutionResult:
1117+
"""Execute a GraphQL operation synchronously.
1118+
1119+
Also implements the "Evaluating requests" section of the GraphQL specification.
1120+
1121+
However, it guarantees to complete synchronously (or throw an error) assuming
1122+
that all field resolvers are also synchronous.
1123+
1124+
Set check_sync to True to still run checks that no awaitable values are returned.
1125+
"""
1126+
is_awaitable = (
1127+
check_sync
1128+
if callable(check_sync)
1129+
else (None if check_sync else assume_not_awaitable)
1130+
)
1131+
1132+
result = execute(
1133+
schema,
1134+
document,
1135+
root_value,
1136+
context_value,
1137+
variable_values,
1138+
operation_name,
1139+
field_resolver,
1140+
type_resolver,
1141+
middleware,
1142+
execution_context_class,
1143+
is_awaitable,
1144+
)
1145+
1146+
# Assert that the execution was synchronous.
1147+
if isawaitable(result):
1148+
ensure_future(cast(Awaitable[ExecutionResult], result)).cancel()
1149+
raise RuntimeError("GraphQL execution failed to complete synchronously.")
1150+
1151+
return cast(ExecutionResult, result)
1152+
1153+
10971154
def assert_valid_execution_arguments(
10981155
schema: GraphQLSchema,
10991156
document: DocumentNode,

Diff for: src/graphql/utilities/introspection_from_schema.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ def introspection_from_schema(
3232
)
3333
)
3434

35-
from ..execution.execute import execute, ExecutionResult
35+
from ..execution.execute import execute_sync, ExecutionResult
3636

37-
result = execute(schema, document)
37+
result = execute_sync(schema, document)
3838
if not isinstance(result, ExecutionResult): # pragma: no cover
3939
raise RuntimeError("Introspection cannot be executed")
4040
if result.errors: # pragma: no cover

Diff for: tests/benchmarks/test_introspection_from_schema.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from graphql import build_schema, parse, execute
1+
from graphql import build_schema, parse, execute_sync
22
from graphql.utilities import get_introspection_query
33

44
from ..fixtures import big_schema_sdl # noqa: F401
@@ -7,5 +7,5 @@
77
def test_execute_introspection_query(benchmark, big_schema_sdl): # noqa: F811
88
schema = build_schema(big_schema_sdl, assume_valid=True)
99
document = parse(get_introspection_query())
10-
result = benchmark(lambda: execute(schema=schema, document=document))
10+
result = benchmark(lambda: execute_sync(schema=schema, document=document))
1111
assert result.errors is None

Diff for: tests/execution/test_abstract.py

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import NamedTuple
22

3-
from graphql import graphql_sync
43
from graphql.error import format_error
4+
from graphql.execution import execute_sync
5+
from graphql.language import parse
56
from graphql.type import (
67
GraphQLBoolean,
78
GraphQLField,
@@ -89,7 +90,8 @@ def is_type_of_used_to_resolve_runtime_type_for_interface():
8990
types=[CatType, DogType],
9091
)
9192

92-
query = """
93+
document = parse(
94+
"""
9395
{
9496
pets {
9597
name
@@ -102,8 +104,9 @@ def is_type_of_used_to_resolve_runtime_type_for_interface():
102104
}
103105
}
104106
"""
107+
)
105108

106-
result = graphql_sync(schema, query)
109+
result = execute_sync(schema, document)
107110
assert result == (
108111
{
109112
"pets": [
@@ -150,7 +153,8 @@ def is_type_of_used_to_resolve_runtime_type_for_union():
150153
)
151154
)
152155

153-
query = """
156+
document = parse(
157+
"""
154158
{
155159
pets {
156160
... on Dog {
@@ -164,8 +168,9 @@ def is_type_of_used_to_resolve_runtime_type_for_union():
164168
}
165169
}
166170
"""
171+
)
167172

168-
result = graphql_sync(schema, query)
173+
result = execute_sync(schema, document)
169174
assert result == (
170175
{
171176
"pets": [
@@ -226,7 +231,8 @@ def resolve_type_on_interface_yields_useful_error():
226231
types=[CatType, DogType],
227232
)
228233

229-
query = """
234+
document = parse(
235+
"""
230236
{
231237
pets {
232238
name
@@ -239,8 +245,9 @@ def resolve_type_on_interface_yields_useful_error():
239245
}
240246
}
241247
"""
248+
)
242249

243-
result = graphql_sync(schema, query)
250+
result = execute_sync(schema, document)
244251
assert result.data == {
245252
"pets": [
246253
{"name": "Odie", "woofs": True},
@@ -301,7 +308,8 @@ def resolve_type_on_union_yields_useful_error():
301308
)
302309
)
303310

304-
query = """
311+
document = parse(
312+
"""
305313
{
306314
pets {
307315
... on Dog {
@@ -315,8 +323,9 @@ def resolve_type_on_union_yields_useful_error():
315323
}
316324
}
317325
"""
326+
)
318327

319-
result = graphql_sync(schema, query)
328+
result = execute_sync(schema, document)
320329
assert result.data == {
321330
"pets": [
322331
{"name": "Odie", "woofs": True},
@@ -355,7 +364,8 @@ def returning_invalid_value_from_resolve_type_yields_useful_error():
355364
types=[foo_object],
356365
)
357366

358-
result = graphql_sync(schema, "{ foo { bar } }")
367+
document = parse("{ foo { bar } }")
368+
result = execute_sync(schema, document)
359369

360370
assert result == (
361371
{"foo": None},
@@ -391,7 +401,8 @@ def missing_both_resolve_type_and_is_type_of_yields_useful_error():
391401
types=[foo_object],
392402
)
393403

394-
result = graphql_sync(schema, "{ foo { bar } }")
404+
document = parse("{ foo { bar } }")
405+
result = execute_sync(schema, document)
395406

396407
assert result == (
397408
{"foo": None},
@@ -446,7 +457,8 @@ def resolve_type_allows_resolving_with_type_name():
446457
types=[CatType, DogType],
447458
)
448459

449-
query = """
460+
document = parse(
461+
"""
450462
{
451463
pets {
452464
name
@@ -458,8 +470,9 @@ def resolve_type_allows_resolving_with_type_name():
458470
}
459471
}
460472
}"""
473+
)
461474

462-
result = graphql_sync(schema, query)
475+
result = execute_sync(schema, document)
463476
assert result == (
464477
{
465478
"pets": [

Diff for: tests/execution/test_directives.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from graphql.execution import execute, ExecutionResult
1+
from graphql.execution import execute_sync, ExecutionResult
22
from graphql.language import parse
3-
from graphql.pyutils import AwaitableOrValue
43
from graphql.type import GraphQLObjectType, GraphQLField, GraphQLSchema, GraphQLString
54

65
schema = GraphQLSchema(
@@ -19,9 +18,9 @@ def b(self, *_args):
1918
return "b"
2019

2120

22-
def execute_test_query(query: str) -> AwaitableOrValue[ExecutionResult]:
21+
def execute_test_query(query: str) -> ExecutionResult:
2322
document = parse(query)
24-
return execute(schema, document, RootValue())
23+
return execute_sync(schema, document, RootValue())
2524

2625

2726
def describe_execute_handles_directives():

0 commit comments

Comments
 (0)