11from asyncio import gather
2- from inspect import isawaitable
32from typing import (
43 Any ,
54 Awaitable ,
5+ Callable ,
66 Dict ,
77 Iterable ,
88 List ,
2828)
2929from ..pyutils import (
3030 inspect ,
31+ is_awaitable as default_is_awaitable ,
3132 AwaitableOrValue ,
3233 FrozenList ,
3334 Path ,
@@ -110,64 +111,6 @@ class ExecutionResult(NamedTuple):
110111Middleware = Optional [Union [Tuple , List , MiddlewareManager ]]
111112
112113
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-
171114class ExecutionContext :
172115 """Data that must be available at all points during query execution.
173116
@@ -186,6 +129,8 @@ class ExecutionContext:
186129 errors : List [GraphQLError ]
187130 middleware_manager : Optional [MiddlewareManager ]
188131
132+ is_awaitable = staticmethod (default_is_awaitable )
133+
189134 def __init__ (
190135 self ,
191136 schema : GraphQLSchema ,
@@ -198,6 +143,7 @@ def __init__(
198143 type_resolver : GraphQLTypeResolver ,
199144 errors : List [GraphQLError ],
200145 middleware_manager : Optional [MiddlewareManager ],
146+ is_awaitable : Optional [Callable [[Any ], bool ]],
201147 ) -> None :
202148 self .schema = schema
203149 self .fragments = fragments
@@ -209,6 +155,8 @@ def __init__(
209155 self .type_resolver = type_resolver # type: ignore
210156 self .errors = errors
211157 self .middleware_manager = middleware_manager
158+ if is_awaitable :
159+ self .is_awaitable = is_awaitable
212160 self ._subfields_cache : Dict [
213161 Tuple [GraphQLObjectType , int ], Dict [str , List [FieldNode ]]
214162 ] = {}
@@ -225,6 +173,7 @@ def build(
225173 field_resolver : Optional [GraphQLFieldResolver ] = None ,
226174 type_resolver : Optional [GraphQLTypeResolver ] = None ,
227175 middleware : Optional [Middleware ] = None ,
176+ is_awaitable : Optional [Callable [[Any ], bool ]] = None ,
228177 ) -> Union [List [GraphQLError ], "ExecutionContext" ]:
229178 """Build an execution context
230179
@@ -292,6 +241,7 @@ def build(
292241 type_resolver or default_type_resolver ,
293242 [],
294243 middleware_manager ,
244+ is_awaitable ,
295245 )
296246
297247 def build_response (
@@ -302,7 +252,7 @@ def build_response(
302252 Given a completed execution context and data, build the (data, errors) response
303253 defined by the "Response" section of the GraphQL spec.
304254 """
305- if isawaitable (data ):
255+ if self . is_awaitable (data ):
306256
307257 async def build_response_async ():
308258 return self .build_response (await data ) # type: ignore
@@ -346,7 +296,7 @@ def execute_operation(
346296 self .errors .append (error )
347297 return None
348298 else :
349- if isawaitable (result ):
299+ if self . is_awaitable (result ):
350300 # noinspection PyShadowingNames
351301 async def await_result ():
352302 try :
@@ -369,27 +319,28 @@ def execute_fields_serially(
369319 Implements the "Evaluating selection sets" section of the spec for "write" mode.
370320 """
371321 results : Dict [str , Any ] = {}
322+ is_awaitable = self .is_awaitable
372323 for response_name , field_nodes in fields .items ():
373324 field_path = Path (path , response_name )
374325 result = self .resolve_field (
375326 parent_type , source_value , field_nodes , field_path
376327 )
377328 if result is Undefined :
378329 continue
379- if isawaitable (results ):
330+ if is_awaitable (results ):
380331 # noinspection PyShadowingNames
381332 async def await_and_set_result (results , response_name , result ):
382333 awaited_results = await results
383334 awaited_results [response_name ] = (
384- await result if isawaitable (result ) else result
335+ await result if is_awaitable (result ) else result
385336 )
386337 return awaited_results
387338
388339 # noinspection PyTypeChecker
389340 results = await_and_set_result (
390341 cast (Awaitable , results ), response_name , result
391342 )
392- elif isawaitable (result ):
343+ elif is_awaitable (result ):
393344 # noinspection PyShadowingNames
394345 async def set_result (results , response_name , result ):
395346 results [response_name ] = await result
@@ -399,7 +350,7 @@ async def set_result(results, response_name, result):
399350 results = set_result (results , response_name , result )
400351 else :
401352 results [response_name ] = result
402- if isawaitable (results ):
353+ if is_awaitable (results ):
403354 # noinspection PyShadowingNames
404355 async def get_results ():
405356 return await cast (Awaitable , results )
@@ -419,6 +370,7 @@ def execute_fields(
419370 Implements the "Evaluating selection sets" section of the spec for "read" mode.
420371 """
421372 results = {}
373+ is_awaitable = self .is_awaitable
422374 awaitable_fields : List [str ] = []
423375 append_awaitable = awaitable_fields .append
424376 for response_name , field_nodes in fields .items ():
@@ -428,7 +380,7 @@ def execute_fields(
428380 )
429381 if result is not Undefined :
430382 results [response_name ] = result
431- if isawaitable (result ):
383+ if is_awaitable (result ):
432384 append_awaitable (response_name )
433385
434386 # If there are no coroutines, we can just return the object
@@ -564,6 +516,7 @@ def build_resolve_info(
564516 self .operation ,
565517 self .variable_values ,
566518 self .context_value ,
519+ self .is_awaitable ,
567520 )
568521
569522 def resolve_field (
@@ -626,7 +579,7 @@ def resolve_field_value_or_error(
626579 # Note that contrary to the JavaScript implementation, we pass the context
627580 # value as part of the resolve info.
628581 result = resolve_fn (source , info , ** args )
629- if isawaitable (result ):
582+ if self . is_awaitable (result ):
630583 # noinspection PyShadowingNames
631584 async def await_result ():
632585 try :
@@ -657,13 +610,13 @@ def complete_value_catching_error(
657610 the execution context.
658611 """
659612 try :
660- if isawaitable (result ):
613+ if self . is_awaitable (result ):
661614
662615 async def await_result ():
663616 value = self .complete_value (
664617 return_type , field_nodes , info , path , await result
665618 )
666- if isawaitable (value ):
619+ if self . is_awaitable (value ):
667620 return await value
668621 return value
669622
@@ -672,7 +625,7 @@ async def await_result():
672625 completed = self .complete_value (
673626 return_type , field_nodes , info , path , result
674627 )
675- if isawaitable (completed ):
628+ if self . is_awaitable (completed ):
676629 # noinspection PyShadowingNames
677630 async def await_completed ():
678631 try :
@@ -810,6 +763,7 @@ def complete_list_value(
810763 # the list contains no coroutine objects by avoiding creating another coroutine
811764 # object.
812765 item_type = return_type .of_type
766+ is_awaitable = self .is_awaitable
813767 awaitable_indices : List [int ] = []
814768 append_awaitable = awaitable_indices .append
815769 completed_results : List [Any ] = []
@@ -822,7 +776,7 @@ def complete_list_value(
822776 item_type , field_nodes , info , field_path , item
823777 )
824778
825- if isawaitable (completed_item ):
779+ if is_awaitable (completed_item ):
826780 append_awaitable (index )
827781 append_result (completed_item )
828782
@@ -873,7 +827,7 @@ def complete_abstract_value(
873827 resolve_type_fn = return_type .resolve_type or self .type_resolver
874828 runtime_type = resolve_type_fn (result , info , return_type ) # type: ignore
875829
876- if isawaitable (runtime_type ):
830+ if self . is_awaitable (runtime_type ):
877831
878832 async def await_complete_object_value ():
879833 value = self .complete_object_value (
@@ -889,7 +843,7 @@ async def await_complete_object_value():
889843 path ,
890844 result ,
891845 )
892- if isawaitable (value ):
846+ if self . is_awaitable (value ):
893847 return await value # type: ignore
894848 return value
895849
@@ -957,7 +911,7 @@ def complete_object_value(
957911 if return_type .is_type_of :
958912 is_type_of = return_type .is_type_of (result , info )
959913
960- if isawaitable (is_type_of ):
914+ if self . is_awaitable (is_type_of ):
961915
962916 async def collect_and_execute_subfields_async ():
963917 if not await is_type_of : # type: ignore
@@ -1018,6 +972,66 @@ def collect_subfields(
1018972 return sub_field_nodes
1019973
1020974
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+
10211035def assert_valid_execution_arguments (
10221036 schema : GraphQLSchema ,
10231037 document : DocumentNode ,
@@ -1116,6 +1130,7 @@ def default_type_resolver(
11161130
11171131 # Otherwise, test each possible type.
11181132 possible_types = info .schema .get_possible_types (abstract_type )
1133+ is_awaitable = info .is_awaitable
11191134 awaitable_is_type_of_results : List [Awaitable ] = []
11201135 append_awaitable_results = awaitable_is_type_of_results .append
11211136 awaitable_types : List [GraphQLObjectType ] = []
@@ -1125,7 +1140,7 @@ def default_type_resolver(
11251140 if type_ .is_type_of :
11261141 is_type_of_result = type_ .is_type_of (value , info )
11271142
1128- if isawaitable (is_type_of_result ):
1143+ if is_awaitable (is_type_of_result ):
11291144 append_awaitable_results (cast (Awaitable , is_type_of_result ))
11301145 append_awaitable_types (type_ )
11311146 elif is_type_of_result :
0 commit comments