1111 Generator ,
1212 Iterable ,
1313 Sequence ,
14+ Union ,
1415 cast ,
1516)
1617
1718from graphql .execution .types import (
18- is_deferred_fragment_record ,
19+ SubsequentResultRecord ,
1920 is_deferred_grouped_field_set_record ,
2021)
2122
3031 ReconcilableDeferredGroupedFieldSetResult ,
3132 StreamItemsRecord ,
3233 StreamItemsResult ,
33- SubsequentResultRecord ,
3434 )
3535
36+ try :
37+ from typing import TypeGuard
38+ except ImportError : # Python < 3.10
39+ from typing_extensions import TypeGuard
40+
3641__all__ = ["IncrementalGraph" ]
3742
3843
44+ class DeferredFragmentNode :
45+ """A node representing a deferred fragment in the incremental graph."""
46+
47+ __slots__ = (
48+ "children" ,
49+ "deferred_fragment_record" ,
50+ "expected_reconcilable_results" ,
51+ "reconcilable_results" ,
52+ "results" ,
53+ )
54+
55+ deferred_fragment_record : DeferredFragmentRecord
56+ expected_reconcilable_results : int
57+ results : list [DeferredGroupedFieldSetResult ]
58+ reconcilable_results : list [ReconcilableDeferredGroupedFieldSetResult ]
59+ children : list [DeferredFragmentNode ]
60+
61+ def __init__ (self , deferred_fragment_record : DeferredFragmentRecord ) -> None :
62+ """Initialize the DeferredFragmentNode."""
63+ self .deferred_fragment_record = deferred_fragment_record
64+ self .expected_reconcilable_results = 0
65+ self .results = []
66+ self .reconcilable_results = []
67+ self .children = []
68+
69+
70+ SubsequentResultNode = Union [DeferredFragmentNode , SubsequentResultRecord ]
71+
72+
73+ def is_deferred_fragment_node (
74+ node : DeferredFragmentNode | None ,
75+ ) -> TypeGuard [DeferredFragmentNode ]:
76+ """Check whether the given node is a deferred fragment node."""
77+ return isinstance (node , DeferredFragmentNode )
78+
79+
80+ def is_stream_node (
81+ node : SubsequentResultNode | None ,
82+ ) -> TypeGuard [SubsequentResultRecord ]:
83+ """Check whether the given result node is a stream node."""
84+ return isinstance (node , SubsequentResultRecord )
85+
86+
3987class IncrementalGraph :
4088 """Helper class to execute incremental Graphs.
4189
4290 For internal use only.
4391 """
4492
45- _pending : dict [SubsequentResultRecord , None ]
46- _new_pending : dict [SubsequentResultRecord , None ]
93+ _pending : dict [SubsequentResultNode , None ]
94+ _deferred_fragment_nodes : dict [DeferredFragmentRecord , DeferredFragmentNode ]
95+ _new_pending : dict [SubsequentResultNode , None ]
4796 _completed_queue : list [IncrementalDataRecordResult ]
4897 _next_queue : list [Future [Iterable [IncrementalDataRecordResult ]]]
4998
@@ -52,6 +101,7 @@ class IncrementalGraph:
52101 def __init__ (self ) -> None :
53102 """Initialize the IncrementalGraph."""
54103 self ._pending = {}
104+ self ._deferred_fragment_nodes = {}
55105 self ._new_pending = {}
56106 self ._completed_queue = []
57107 self ._next_queue = []
@@ -66,8 +116,10 @@ def add_incremental_data_records(
66116 for deferred_fragment_record in (
67117 incremental_data_record .deferred_fragment_records
68118 ): # pragma: no branch
69- deferred_fragment_record .expected_reconcilable_results += 1
70- self ._add_deferred_fragment_record (deferred_fragment_record )
119+ deferred_fragment_node = self ._add_deferred_fragment_node (
120+ deferred_fragment_record
121+ )
122+ deferred_fragment_node .expected_reconcilable_results += 1
71123
72124 deferred_result = incremental_data_record .result
73125 if is_awaitable (deferred_result ):
@@ -103,6 +155,20 @@ async def enqueue_stream(
103155 else :
104156 self ._enqueue (stream_result ) # type: ignore
105157
158+ def add_completed_reconcilable_deferred_grouped_field_set (
159+ self , reconcilable_result : ReconcilableDeferredGroupedFieldSetResult
160+ ) -> None :
161+ """Add a completed reconcilable deferred grouped field set result."""
162+ deferred_fragment_nodes = filter (
163+ is_deferred_fragment_node ,
164+ map (
165+ self ._deferred_fragment_nodes .get ,
166+ reconcilable_result .deferred_fragment_records ,
167+ ),
168+ )
169+ for deferred_fragment_node in deferred_fragment_nodes :
170+ deferred_fragment_node .reconcilable_results .append (reconcilable_result )
171+
106172 def get_new_pending (self ) -> list [SubsequentResultRecord ]:
107173 """Get new pending subsequent result records."""
108174 _pending , _new_pending = self ._pending , self ._new_pending
@@ -113,17 +179,16 @@ def get_new_pending(self) -> list[SubsequentResultRecord]:
113179 add_iteration = iterate .append
114180 while iterate :
115181 node = iterate .pop (0 )
116- if is_deferred_fragment_record (node ):
117- if node .expected_reconcilable_results :
118- _pending [node ] = None
119- add_result (node )
120- continue
121- for child in node .children :
122- _new_pending [child ] = None
123- add_iteration (child )
124- else :
182+ if is_stream_node (node ):
125183 _pending [node ] = None
126184 add_result (node )
185+ elif node .expected_reconcilable_results : # type: ignore
186+ _pending [node ] = None
187+ add_result (node .deferred_fragment_record ) # type: ignore
188+ else :
189+ for child in node .children : # type: ignore
190+ _new_pending [child ] = None
191+ add_iteration (child )
127192 _new_pending .clear ()
128193 return new_pending
129194
@@ -152,53 +217,87 @@ def complete_deferred_fragment(
152217 deferred_fragment_record : DeferredFragmentRecord ,
153218 ) -> list [ReconcilableDeferredGroupedFieldSetResult ] | None :
154219 """Complete a deferred fragment."""
155- reconcilable_results = deferred_fragment_record .reconcilable_results
156- if deferred_fragment_record .expected_reconcilable_results != len (
220+ try :
221+ deferred_fragment_node = self ._deferred_fragment_nodes [
222+ deferred_fragment_record
223+ ]
224+ except KeyError : # pragma: no cover
225+ return None
226+ reconcilable_results = deferred_fragment_node .reconcilable_results
227+ if deferred_fragment_node .expected_reconcilable_results != len (
157228 reconcilable_results
158229 ):
159230 return None
160- self .remove_subsequent_result_record ( deferred_fragment_record )
231+ self ._remove_pending ( deferred_fragment_node )
161232 new_pending = self ._new_pending
162- for child in deferred_fragment_record .children :
233+ for child in deferred_fragment_node .children :
163234 new_pending [child ] = None
164235 for result in child .results :
165236 self ._enqueue (result )
166237 return reconcilable_results
167238
168- def remove_subsequent_result_record (
239+ def remove_deferred_fragment (
169240 self ,
170- subsequent_result_record : SubsequentResultRecord ,
241+ deferred_fragment_record : DeferredFragmentRecord ,
171242 ) -> None :
172- """Remove a subsequent result record as no longer pending."""
173- del self ._pending [subsequent_result_record ]
243+ """Remove a deferred fragment."""
244+ try :
245+ deferred_fragment_node = self ._deferred_fragment_nodes [
246+ deferred_fragment_record
247+ ]
248+ except KeyError : # pragma: no cover
249+ return
250+ self ._remove_pending (deferred_fragment_node )
251+ for child in deferred_fragment_node .children : # pragma: no cover
252+ self .remove_deferred_fragment (child .deferred_fragment_record )
253+
254+ def remove_stream (self , stream_record : SubsequentResultRecord ) -> None :
255+ """Remove a stream record as no longer pending."""
256+ self ._remove_pending (stream_record )
257+
258+ def _remove_pending (self , subsequent_result_node : SubsequentResultNode ) -> None :
259+ """Remove a subsequent result node as no longer pending."""
260+ del self ._pending [subsequent_result_node ]
174261 if not self ._pending :
175262 self .stop_incremental_data ()
176263
177- def _add_deferred_fragment_record (
264+ def _add_deferred_fragment_node (
178265 self , deferred_fragment_record : DeferredFragmentRecord
179- ) -> None :
180- """Add deferred fragment record."""
181- parent = deferred_fragment_record .parent
182- if parent is None :
183- if deferred_fragment_record .id is not None :
184- return
185- self ._new_pending [deferred_fragment_record ] = None
186- return
187- if deferred_fragment_record in parent .children :
188- return
189- parent .children [deferred_fragment_record ] = None
190- self ._add_deferred_fragment_record (parent )
266+ ) -> DeferredFragmentNode :
267+ """Add a deferred fragment node."""
268+ try :
269+ deferred_fragment_node = self ._deferred_fragment_nodes [
270+ deferred_fragment_record
271+ ]
272+ except KeyError :
273+ deferred_fragment_node = DeferredFragmentNode (deferred_fragment_record )
274+ self ._deferred_fragment_nodes [deferred_fragment_record ] = (
275+ deferred_fragment_node
276+ )
277+ parent = deferred_fragment_record .parent
278+ if parent is None :
279+ self ._new_pending [deferred_fragment_node ] = None
280+ else :
281+ parent_node = self ._add_deferred_fragment_node (parent )
282+ parent_node .children .append (deferred_fragment_node )
283+ return deferred_fragment_node
191284
192285 def _enqueue_completed_deferred_grouped_field_set (
193286 self , result : DeferredGroupedFieldSetResult
194287 ) -> None :
195288 """Enqueue completed deferred grouped field set result."""
196- has_pending_parent = False
289+ is_pending = False
197290 for deferred_fragment_record in result .deferred_fragment_records :
198- if deferred_fragment_record .id is not None :
199- has_pending_parent = True
200- deferred_fragment_record .results .append (result )
201- if has_pending_parent :
291+ try :
292+ deferred_fragment_node = self ._deferred_fragment_nodes [
293+ deferred_fragment_record
294+ ]
295+ except KeyError : # pragma: no cover
296+ continue
297+ if deferred_fragment_node in self ._pending :
298+ is_pending = True
299+ deferred_fragment_node .results .append (result )
300+ if is_pending :
202301 self ._enqueue (result )
203302
204303 def _add_task (self , awaitable : Awaitable [Any ]) -> None :
0 commit comments