1
1
from asyncio import gather
2
- from inspect import isawaitable
3
2
from typing import (
4
3
Any ,
5
4
Awaitable ,
5
+ Callable ,
6
6
Dict ,
7
7
Iterable ,
8
8
List ,
28
28
)
29
29
from ..pyutils import (
30
30
inspect ,
31
+ is_awaitable as default_is_awaitable ,
31
32
AwaitableOrValue ,
32
33
FrozenList ,
33
34
Path ,
@@ -110,64 +111,6 @@ class ExecutionResult(NamedTuple):
110
111
Middleware = Optional [Union [Tuple , List , MiddlewareManager ]]
111
112
112
113
113
- def execute (
114
- schema : GraphQLSchema ,
115
- document : DocumentNode ,
116
- root_value : Any = None ,
117
- context_value : Any = None ,
118
- variable_values : Optional [Dict [str , Any ]] = None ,
119
- operation_name : Optional [str ] = None ,
120
- field_resolver : Optional [GraphQLFieldResolver ] = None ,
121
- type_resolver : Optional [GraphQLTypeResolver ] = None ,
122
- middleware : Optional [Middleware ] = None ,
123
- execution_context_class : Optional [Type ["ExecutionContext" ]] = None ,
124
- ) -> AwaitableOrValue [ExecutionResult ]:
125
- """Execute a GraphQL operation.
126
-
127
- Implements the "Evaluating requests" section of the GraphQL specification.
128
-
129
- Returns an ExecutionResult (if all encountered resolvers are synchronous),
130
- or a coroutine object eventually yielding an ExecutionResult.
131
-
132
- If the arguments to this function do not result in a legal execution context,
133
- a GraphQLError will be thrown immediately explaining the invalid input.
134
- """
135
- # If arguments are missing or incorrect, throw an error.
136
- assert_valid_execution_arguments (schema , document , variable_values )
137
-
138
- if execution_context_class is None :
139
- execution_context_class = ExecutionContext
140
-
141
- # If a valid execution context cannot be created due to incorrect arguments,
142
- # a "Response" with only errors is returned.
143
- exe_context = execution_context_class .build (
144
- schema ,
145
- document ,
146
- root_value ,
147
- context_value ,
148
- variable_values ,
149
- operation_name ,
150
- field_resolver ,
151
- type_resolver ,
152
- middleware ,
153
- )
154
-
155
- # Return early errors if execution context failed.
156
- if isinstance (exe_context , list ):
157
- return ExecutionResult (data = None , errors = exe_context )
158
-
159
- # Return a possible coroutine object that will eventually yield the data described
160
- # by the "Response" section of the GraphQL specification.
161
- #
162
- # If errors are encountered while executing a GraphQL field, only that field and
163
- # its descendants will be omitted, and sibling fields will still be executed. An
164
- # execution which encounters errors will still result in a coroutine object that
165
- # can be executed without errors.
166
-
167
- data = exe_context .execute_operation (exe_context .operation , root_value )
168
- return exe_context .build_response (data )
169
-
170
-
171
114
class ExecutionContext :
172
115
"""Data that must be available at all points during query execution.
173
116
@@ -186,6 +129,8 @@ class ExecutionContext:
186
129
errors : List [GraphQLError ]
187
130
middleware_manager : Optional [MiddlewareManager ]
188
131
132
+ is_awaitable = staticmethod (default_is_awaitable )
133
+
189
134
def __init__ (
190
135
self ,
191
136
schema : GraphQLSchema ,
@@ -198,6 +143,7 @@ def __init__(
198
143
type_resolver : GraphQLTypeResolver ,
199
144
errors : List [GraphQLError ],
200
145
middleware_manager : Optional [MiddlewareManager ],
146
+ is_awaitable : Optional [Callable [[Any ], bool ]],
201
147
) -> None :
202
148
self .schema = schema
203
149
self .fragments = fragments
@@ -209,6 +155,8 @@ def __init__(
209
155
self .type_resolver = type_resolver # type: ignore
210
156
self .errors = errors
211
157
self .middleware_manager = middleware_manager
158
+ if is_awaitable :
159
+ self .is_awaitable = is_awaitable
212
160
self ._subfields_cache : Dict [
213
161
Tuple [GraphQLObjectType , int ], Dict [str , List [FieldNode ]]
214
162
] = {}
@@ -225,6 +173,7 @@ def build(
225
173
field_resolver : Optional [GraphQLFieldResolver ] = None ,
226
174
type_resolver : Optional [GraphQLTypeResolver ] = None ,
227
175
middleware : Optional [Middleware ] = None ,
176
+ is_awaitable : Optional [Callable [[Any ], bool ]] = None ,
228
177
) -> Union [List [GraphQLError ], "ExecutionContext" ]:
229
178
"""Build an execution context
230
179
@@ -292,6 +241,7 @@ def build(
292
241
type_resolver or default_type_resolver ,
293
242
[],
294
243
middleware_manager ,
244
+ is_awaitable ,
295
245
)
296
246
297
247
def build_response (
@@ -302,7 +252,7 @@ def build_response(
302
252
Given a completed execution context and data, build the (data, errors) response
303
253
defined by the "Response" section of the GraphQL spec.
304
254
"""
305
- if isawaitable (data ):
255
+ if self . is_awaitable (data ):
306
256
307
257
async def build_response_async ():
308
258
return self .build_response (await data ) # type: ignore
@@ -346,7 +296,7 @@ def execute_operation(
346
296
self .errors .append (error )
347
297
return None
348
298
else :
349
- if isawaitable (result ):
299
+ if self . is_awaitable (result ):
350
300
# noinspection PyShadowingNames
351
301
async def await_result ():
352
302
try :
@@ -369,27 +319,28 @@ def execute_fields_serially(
369
319
Implements the "Evaluating selection sets" section of the spec for "write" mode.
370
320
"""
371
321
results : Dict [str , Any ] = {}
322
+ is_awaitable = self .is_awaitable
372
323
for response_name , field_nodes in fields .items ():
373
324
field_path = Path (path , response_name )
374
325
result = self .resolve_field (
375
326
parent_type , source_value , field_nodes , field_path
376
327
)
377
328
if result is Undefined :
378
329
continue
379
- if isawaitable (results ):
330
+ if is_awaitable (results ):
380
331
# noinspection PyShadowingNames
381
332
async def await_and_set_result (results , response_name , result ):
382
333
awaited_results = await results
383
334
awaited_results [response_name ] = (
384
- await result if isawaitable (result ) else result
335
+ await result if is_awaitable (result ) else result
385
336
)
386
337
return awaited_results
387
338
388
339
# noinspection PyTypeChecker
389
340
results = await_and_set_result (
390
341
cast (Awaitable , results ), response_name , result
391
342
)
392
- elif isawaitable (result ):
343
+ elif is_awaitable (result ):
393
344
# noinspection PyShadowingNames
394
345
async def set_result (results , response_name , result ):
395
346
results [response_name ] = await result
@@ -399,7 +350,7 @@ async def set_result(results, response_name, result):
399
350
results = set_result (results , response_name , result )
400
351
else :
401
352
results [response_name ] = result
402
- if isawaitable (results ):
353
+ if is_awaitable (results ):
403
354
# noinspection PyShadowingNames
404
355
async def get_results ():
405
356
return await cast (Awaitable , results )
@@ -419,6 +370,7 @@ def execute_fields(
419
370
Implements the "Evaluating selection sets" section of the spec for "read" mode.
420
371
"""
421
372
results = {}
373
+ is_awaitable = self .is_awaitable
422
374
awaitable_fields : List [str ] = []
423
375
append_awaitable = awaitable_fields .append
424
376
for response_name , field_nodes in fields .items ():
@@ -428,7 +380,7 @@ def execute_fields(
428
380
)
429
381
if result is not Undefined :
430
382
results [response_name ] = result
431
- if isawaitable (result ):
383
+ if is_awaitable (result ):
432
384
append_awaitable (response_name )
433
385
434
386
# If there are no coroutines, we can just return the object
@@ -564,6 +516,7 @@ def build_resolve_info(
564
516
self .operation ,
565
517
self .variable_values ,
566
518
self .context_value ,
519
+ self .is_awaitable ,
567
520
)
568
521
569
522
def resolve_field (
@@ -626,7 +579,7 @@ def resolve_field_value_or_error(
626
579
# Note that contrary to the JavaScript implementation, we pass the context
627
580
# value as part of the resolve info.
628
581
result = resolve_fn (source , info , ** args )
629
- if isawaitable (result ):
582
+ if self . is_awaitable (result ):
630
583
# noinspection PyShadowingNames
631
584
async def await_result ():
632
585
try :
@@ -657,13 +610,13 @@ def complete_value_catching_error(
657
610
the execution context.
658
611
"""
659
612
try :
660
- if isawaitable (result ):
613
+ if self . is_awaitable (result ):
661
614
662
615
async def await_result ():
663
616
value = self .complete_value (
664
617
return_type , field_nodes , info , path , await result
665
618
)
666
- if isawaitable (value ):
619
+ if self . is_awaitable (value ):
667
620
return await value
668
621
return value
669
622
@@ -672,7 +625,7 @@ async def await_result():
672
625
completed = self .complete_value (
673
626
return_type , field_nodes , info , path , result
674
627
)
675
- if isawaitable (completed ):
628
+ if self . is_awaitable (completed ):
676
629
# noinspection PyShadowingNames
677
630
async def await_completed ():
678
631
try :
@@ -810,6 +763,7 @@ def complete_list_value(
810
763
# the list contains no coroutine objects by avoiding creating another coroutine
811
764
# object.
812
765
item_type = return_type .of_type
766
+ is_awaitable = self .is_awaitable
813
767
awaitable_indices : List [int ] = []
814
768
append_awaitable = awaitable_indices .append
815
769
completed_results : List [Any ] = []
@@ -822,7 +776,7 @@ def complete_list_value(
822
776
item_type , field_nodes , info , field_path , item
823
777
)
824
778
825
- if isawaitable (completed_item ):
779
+ if is_awaitable (completed_item ):
826
780
append_awaitable (index )
827
781
append_result (completed_item )
828
782
@@ -873,7 +827,7 @@ def complete_abstract_value(
873
827
resolve_type_fn = return_type .resolve_type or self .type_resolver
874
828
runtime_type = resolve_type_fn (result , info , return_type ) # type: ignore
875
829
876
- if isawaitable (runtime_type ):
830
+ if self . is_awaitable (runtime_type ):
877
831
878
832
async def await_complete_object_value ():
879
833
value = self .complete_object_value (
@@ -889,7 +843,7 @@ async def await_complete_object_value():
889
843
path ,
890
844
result ,
891
845
)
892
- if isawaitable (value ):
846
+ if self . is_awaitable (value ):
893
847
return await value # type: ignore
894
848
return value
895
849
@@ -957,7 +911,7 @@ def complete_object_value(
957
911
if return_type .is_type_of :
958
912
is_type_of = return_type .is_type_of (result , info )
959
913
960
- if isawaitable (is_type_of ):
914
+ if self . is_awaitable (is_type_of ):
961
915
962
916
async def collect_and_execute_subfields_async ():
963
917
if not await is_type_of : # type: ignore
@@ -1018,6 +972,66 @@ def collect_subfields(
1018
972
return sub_field_nodes
1019
973
1020
974
975
+ def execute (
976
+ schema : GraphQLSchema ,
977
+ document : DocumentNode ,
978
+ root_value : Any = None ,
979
+ context_value : Any = None ,
980
+ variable_values : Optional [Dict [str , Any ]] = None ,
981
+ operation_name : Optional [str ] = None ,
982
+ field_resolver : Optional [GraphQLFieldResolver ] = None ,
983
+ type_resolver : Optional [GraphQLTypeResolver ] = None ,
984
+ middleware : Optional [Middleware ] = None ,
985
+ execution_context_class : Optional [Type ["ExecutionContext" ]] = None ,
986
+ is_awaitable : Optional [Callable [[Any ], bool ]] = None ,
987
+ ) -> AwaitableOrValue [ExecutionResult ]:
988
+ """Execute a GraphQL operation.
989
+
990
+ Implements the "Evaluating requests" section of the GraphQL specification.
991
+
992
+ Returns an ExecutionResult (if all encountered resolvers are synchronous),
993
+ or a coroutine object eventually yielding an ExecutionResult.
994
+
995
+ If the arguments to this function do not result in a legal execution context,
996
+ a GraphQLError will be thrown immediately explaining the invalid input.
997
+ """
998
+ # If arguments are missing or incorrect, throw an error.
999
+ assert_valid_execution_arguments (schema , document , variable_values )
1000
+
1001
+ if execution_context_class is None :
1002
+ execution_context_class = ExecutionContext
1003
+
1004
+ # If a valid execution context cannot be created due to incorrect arguments,
1005
+ # a "Response" with only errors is returned.
1006
+ exe_context = execution_context_class .build (
1007
+ schema ,
1008
+ document ,
1009
+ root_value ,
1010
+ context_value ,
1011
+ variable_values ,
1012
+ operation_name ,
1013
+ field_resolver ,
1014
+ type_resolver ,
1015
+ middleware ,
1016
+ is_awaitable ,
1017
+ )
1018
+
1019
+ # Return early errors if execution context failed.
1020
+ if isinstance (exe_context , list ):
1021
+ return ExecutionResult (data = None , errors = exe_context )
1022
+
1023
+ # Return a possible coroutine object that will eventually yield the data described
1024
+ # by the "Response" section of the GraphQL specification.
1025
+ #
1026
+ # If errors are encountered while executing a GraphQL field, only that field and
1027
+ # its descendants will be omitted, and sibling fields will still be executed. An
1028
+ # execution which encounters errors will still result in a coroutine object that
1029
+ # can be executed without errors.
1030
+
1031
+ data = exe_context .execute_operation (exe_context .operation , root_value )
1032
+ return exe_context .build_response (data )
1033
+
1034
+
1021
1035
def assert_valid_execution_arguments (
1022
1036
schema : GraphQLSchema ,
1023
1037
document : DocumentNode ,
@@ -1116,6 +1130,7 @@ def default_type_resolver(
1116
1130
1117
1131
# Otherwise, test each possible type.
1118
1132
possible_types = info .schema .get_possible_types (abstract_type )
1133
+ is_awaitable = info .is_awaitable
1119
1134
awaitable_is_type_of_results : List [Awaitable ] = []
1120
1135
append_awaitable_results = awaitable_is_type_of_results .append
1121
1136
awaitable_types : List [GraphQLObjectType ] = []
@@ -1125,7 +1140,7 @@ def default_type_resolver(
1125
1140
if type_ .is_type_of :
1126
1141
is_type_of_result = type_ .is_type_of (value , info )
1127
1142
1128
- if isawaitable (is_type_of_result ):
1143
+ if is_awaitable (is_type_of_result ):
1129
1144
append_awaitable_results (cast (Awaitable , is_type_of_result ))
1130
1145
append_awaitable_types (type_ )
1131
1146
elif is_type_of_result :
0 commit comments