140140 "Executor" ,
141141 "GraphQLWrappedResult" ,
142142 "Middleware" ,
143+ "RootSelectionSetExecutor" ,
143144 "create_source_event_stream" ,
144145 "default_field_resolver" ,
145146 "default_type_resolver" ,
148149 "execute_subscription_event" ,
149150 "execute_sync" ,
150151 "experimental_execute_incrementally" ,
152+ "map_source_to_response_event" ,
151153 "subscribe" ,
152154]
153155
@@ -237,7 +239,6 @@ class Executor(IncrementalPublisherContext):
237239 field_resolver : GraphQLFieldResolver
238240 type_resolver : GraphQLTypeResolver
239241 subscribe_field_resolver : GraphQLFieldResolver
240- per_event_executor : Callable [[Executor ], AwaitableOrValue [ExecutionResult ]]
241242 enable_early_execution : bool
242243 hide_suggestions : bool
243244 abort_signal : AbortSignal | None
@@ -274,8 +275,6 @@ def __init__( # noqa: PLR0913
274275 middleware_manager : MiddlewareManager | None = None ,
275276 is_awaitable : Callable [[Any ], TypeGuard [Awaitable ]] | None = None ,
276277 is_async_iterable : Callable [[Any ], TypeGuard [AsyncIterable ]] | None = None ,
277- per_event_executor : Callable [[Executor ], AwaitableOrValue [ExecutionResult ]]
278- | None = None ,
279278 hide_suggestions : bool = False ,
280279 abort_signal : AbortSignal | None = None ,
281280 hooks : ExecutionHooks | None = None ,
@@ -290,7 +289,6 @@ def __init__( # noqa: PLR0913
290289 self .field_resolver = field_resolver
291290 self .type_resolver = type_resolver
292291 self .subscribe_field_resolver = subscribe_field_resolver
293- self .per_event_executor = per_event_executor or execute_subscription_event
294292 self .enable_early_execution = enable_early_execution
295293 self .hide_suggestions = hide_suggestions
296294 self .abort_signal = abort_signal
@@ -331,8 +329,6 @@ def build( # noqa: PLR0913
331329 middleware : Middleware | None = None ,
332330 is_awaitable : Callable [[Any ], TypeGuard [Awaitable ]] | None = None ,
333331 is_async_iterable : Callable [[Any ], TypeGuard [AsyncIterable ]] | None = None ,
334- per_event_executor : Callable [[Executor ], AwaitableOrValue [ExecutionResult ]]
335- | None = None ,
336332 hide_suggestions : bool = False ,
337333 abort_signal : AbortSignal | None = None ,
338334 hooks : ExecutionHooks | None = None ,
@@ -425,7 +421,6 @@ def build( # noqa: PLR0913
425421 middleware_manager ,
426422 is_awaitable ,
427423 is_async_iterable ,
428- per_event_executor = per_event_executor ,
429424 hide_suggestions = hide_suggestions ,
430425 abort_signal = abort_signal ,
431426 hooks = hooks ,
@@ -1978,39 +1973,6 @@ def build_sub_execution_plan(
19781973 execution_plans [original_grouped_field_set ] = exeecution_plan
19791974 return exeecution_plan
19801975
1981- def map_source_to_response (
1982- self , result_or_stream : ExecutionResult | AsyncIterable [Any ]
1983- ) -> AsyncGenerator [ExecutionResult , None ] | ExecutionResult :
1984- """Map source result to response.
1985-
1986- For each payload yielded from a subscription,
1987- map it over the normal GraphQL :func:`~graphql.execution.execute` function,
1988- with ``payload`` as the ``root_value``.
1989- This implements the "MapSourceToResponseEvent" algorithm
1990- described in the GraphQL specification.
1991- Each event is executed with the executor's ``per_event_executor``, which
1992- defaults to :func:`~graphql.execution.execute_subscription_event` (providing
1993- the "ExecuteSubscriptionEvent" algorithm) but can be overridden to set up and
1994- tear down a custom executor around the execution of each event.
1995- """
1996- if not self .is_async_iterable (result_or_stream ):
1997- return cast ("ExecutionResult" , result_or_stream ) # pragma: no cover
1998-
1999- build_executor = self .build_per_event_executor
2000- per_event_executor = self .per_event_executor
2001-
2002- async def callback (payload : Any ) -> ExecutionResult :
2003- result = per_event_executor (build_executor (payload ))
2004- # typecast to ExecutionResult, not possible to return
2005- # ExperimentalIncrementalExecutionResults when operation is 'subscription'.
2006- return (
2007- await cast ("Awaitable[ExecutionResult]" , result )
2008- if self .is_awaitable (result )
2009- else cast ("ExecutionResult" , result )
2010- )
2011-
2012- return map_async_iterable (self .cancellable_iterable (result_or_stream ), callback )
2013-
20141976 def collect_execution_groups (
20151977 self ,
20161978 parent_type : GraphQLObjectType ,
@@ -2924,8 +2886,6 @@ def subscribe(
29242886 enable_early_execution : bool = False ,
29252887 executor_class : type [Executor ] | None = None ,
29262888 middleware : MiddlewareManager | None = None ,
2927- per_event_executor : Callable [[Executor ], AwaitableOrValue [ExecutionResult ]]
2928- | None = None ,
29292889 hide_suggestions : bool = False ,
29302890 ** custom_context_args : Any ,
29312891) -> AwaitableOrValue [AsyncIterator [ExecutionResult ] | ExecutionResult ]:
@@ -2952,10 +2912,12 @@ def subscribe(
29522912 If an operation that defers or streams data is executed with this function,
29532913 a field error will be raised at the location of the `@defer` or `@stream` directive.
29542914
2955- A custom ``per_event_executor`` may be provided to execute each subscription event
2956- with a custom executor. It receives the per-event executor and should
2957- return an ExecutionResult, usually by calling
2958- :func:`~graphql.execution.execute_subscription_event` (the default executor).
2915+ To customize how each subscription event is executed, compose the subscription
2916+ pipeline directly instead of calling this function: build an executor with
2917+ :meth:`Executor.build`, resolve the source event stream with
2918+ :func:`~graphql.execution.create_source_event_stream`, and map it to the response
2919+ stream with :func:`~graphql.execution.map_source_to_response_event`, passing a
2920+ custom ``root_selection_set_executor``.
29592921 """
29602922 if executor_class is None :
29612923 executor_class = Executor
@@ -2975,7 +2937,6 @@ def subscribe(
29752937 max_coercion_errors ,
29762938 enable_early_execution ,
29772939 middleware = middleware ,
2978- per_event_executor = per_event_executor ,
29792940 hide_suggestions = hide_suggestions ,
29802941 ** custom_context_args ,
29812942 )
@@ -2994,16 +2955,19 @@ def subscribe(
29942955
29952956 async def await_result () -> Any :
29962957 awaited_result_or_stream = await result_or_stream
2997- if isinstance (awaited_result_or_stream , ExecutionResult ):
2998- return awaited_result_or_stream
2999- return executor .map_source_to_response (awaited_result_or_stream )
2958+ return (
2959+ map_source_to_response_event (executor , awaited_result_or_stream )
2960+ if executor .is_async_iterable (awaited_result_or_stream )
2961+ else awaited_result_or_stream
2962+ )
30002963
30012964 return await_result ()
30022965
3003- if isinstance (result_or_stream , ExecutionResult ):
3004- return result_or_stream
3005-
3006- return executor .map_source_to_response (result_or_stream ) # type: ignore
2966+ return (
2967+ map_source_to_response_event (executor , result_or_stream ) # type: ignore
2968+ if executor .is_async_iterable (result_or_stream )
2969+ else result_or_stream
2970+ )
30072971
30082972
30092973def execute_root_selection_set (
@@ -3023,17 +2987,59 @@ def execute_subscription_event(
30232987) -> AwaitableOrValue [ExecutionResult ]:
30242988 """Execute a single subscription event.
30252989
3026- This is the default ``per_event_executor`` used by :func:`subscribe`. It provides
3027- the "ExecuteSubscriptionEvent" algorithm described in the GraphQL specification,
3028- which is nearly identical to the "ExecuteQuery" algorithm. A custom executor may
3029- wrap this function to set up and tear down a per-event executor.
2990+ This is the default ``root_selection_set_executor`` used by
2991+ :func:`map_source_to_response_event`. It provides the "ExecuteSubscriptionEvent"
2992+ algorithm described in the GraphQL specification, which is nearly identical to the
2993+ "ExecuteQuery" algorithm. A custom executor may wrap this function to set up and
2994+ tear down a per-event executor.
30302995
30312996 The passed executor should be a per-event executor as created by
30322997 :meth:`Executor.build_per_event_executor`.
30332998 """
30342999 return cast ("AwaitableOrValue[ExecutionResult]" , executor .execute_operation (False ))
30353000
30363001
3002+ RootSelectionSetExecutor : TypeAlias = Callable [
3003+ ["Executor" ], AwaitableOrValue [ExecutionResult ]
3004+ ]
3005+
3006+
3007+ def map_source_to_response_event (
3008+ executor : Executor ,
3009+ source_event_stream : AsyncIterable [Any ],
3010+ root_selection_set_executor : RootSelectionSetExecutor = execute_subscription_event ,
3011+ ) -> AsyncGenerator [ExecutionResult , None ]:
3012+ """Map a subscription source event stream to a response event stream.
3013+
3014+ Implements the "MapSourceToResponseEvent" algorithm described in the GraphQL
3015+ specification, mapping each event from a subscription source event stream to an
3016+ ExecutionResult in the response stream.
3017+
3018+ For each payload yielded from the source event stream, it is mapped over the normal
3019+ GraphQL :func:`~graphql.execution.execute` function, with ``payload`` as the
3020+ ``root_value``. Each event is executed with the given
3021+ ``root_selection_set_executor``, which defaults to
3022+ :func:`~graphql.execution.execute_subscription_event` (providing the
3023+ "ExecuteSubscriptionEvent" algorithm) but can be overridden to set up and tear
3024+ down a custom executor around the execution of each event.
3025+ """
3026+ build_executor = executor .build_per_event_executor
3027+
3028+ async def callback (payload : Any ) -> ExecutionResult :
3029+ result = root_selection_set_executor (build_executor (payload ))
3030+ # typecast to ExecutionResult, not possible to return
3031+ # ExperimentalIncrementalExecutionResults when operation is 'subscription'.
3032+ return (
3033+ await cast ("Awaitable[ExecutionResult]" , result )
3034+ if executor .is_awaitable (result )
3035+ else cast ("ExecutionResult" , result )
3036+ )
3037+
3038+ return map_async_iterable (
3039+ executor .cancellable_iterable (source_event_stream ), callback
3040+ )
3041+
3042+
30373043def create_source_event_stream (
30383044 executor : Executor ,
30393045) -> AwaitableOrValue [AsyncIterable [Any ] | ExecutionResult ]:
0 commit comments