1- from functools import reduce
21from typing import (
32 Any ,
43 Collection ,
2524 get_named_type ,
2625 is_input_object_type ,
2726 is_interface_type ,
28- is_named_type ,
2927 is_object_type ,
3028 is_union_type ,
3129)
@@ -100,7 +98,7 @@ class GraphQLSchema:
10098 ast_node : Optional [ast .SchemaDefinitionNode ]
10199 extension_ast_nodes : Optional [FrozenList [ast .SchemaExtensionNode ]]
102100
103- _implementations : Dict [str , InterfaceImplementations ]
101+ _implementations_map : Dict [str , InterfaceImplementations ]
104102 _sub_type_map : Dict [str , Set [str ]]
105103 _validation_errors : Optional [List [GraphQLError ]]
106104
@@ -119,18 +117,12 @@ def __init__(
119117 """Initialize GraphQL schema.
120118
121119 If this schema was built from a source known to be valid, then it may be marked
122- with `assume_valid` to avoid an additional type system validation. Otherwise
123- check for common mistakes during construction to produce clear and early error
124- messages.
120+ with `assume_valid` to avoid an additional type system validation.
125121 """
126- # If this schema was built from a source known to be valid, then it may be
127- # marked with assume_valid to avoid an additional type system validation.
128122 self ._validation_errors = [] if assume_valid else None
129123
130124 # Check for common mistakes during construction to produce clear and early
131- # error messages.
132- # The query, mutation and subscription types must actually be GraphQL
133- # object types, but we leave it to the validator to report this error.
125+ # error messages, but we leave the specific tests for the validation.
134126 if query and not isinstance (query , GraphQLType ):
135127 raise TypeError ("Expected query to be a GraphQL type." )
136128 if mutation and not isinstance (mutation , GraphQLType ):
@@ -140,28 +132,16 @@ def __init__(
140132 if types is None :
141133 types = []
142134 else :
143- if not is_collection (types ) or (
144- # if reducer has been overridden, don't check types
145- getattr (self .type_map_reducer , "__func__" , None )
146- is GraphQLSchema .type_map_reducer
147- and not all (is_named_type (type_ ) for type_ in types )
135+ if not is_collection (types ) or not all (
136+ isinstance (type_ , GraphQLType ) for type_ in types
148137 ):
149138 raise TypeError (
150- "Schema types must be specified as a collection"
151- " of GraphQLNamedType instances."
139+ "Schema types must be specified as a collection of GraphQL types."
152140 )
153141 if directives is not None :
154142 # noinspection PyUnresolvedReferences
155- if not is_collection (directives ) or (
156- # if reducer has been overridden, don't check directive types
157- getattr (self .type_map_directive_reducer , "__func__" , None )
158- is GraphQLSchema .type_map_directive_reducer
159- and not all (is_directive (directive ) for directive in directives )
160- ):
161- raise TypeError (
162- "Schema directives must be specified as a collection"
163- " of GraphQLDirective instances."
164- )
143+ if not is_collection (directives ):
144+ raise TypeError ("Schema directives must be a collection." )
165145 if not isinstance (directives , FrozenList ):
166146 directives = FrozenList (directives )
167147 if extensions is not None and (
@@ -201,29 +181,85 @@ def __init__(
201181 else cast (FrozenList [GraphQLDirective ], directives )
202182 )
203183
204- # Build type map now to detect any errors within this schema.
205- initial_types : List [Optional [GraphQLNamedType ]] = [
206- query ,
207- mutation ,
208- subscription ,
209- introspection_types ["__Schema" ],
210- ]
184+ # To preserve order of user-provided types, we add first to add them to
185+ # the set of "collected" types, so `collect_referenced_types` ignore them.
211186 if types :
212- initial_types .extend (types )
187+ all_referenced_types = TypeSet .with_initial_types (types )
188+ collect_referenced_types = all_referenced_types .collect_referenced_types
189+ for type_ in types :
190+ # When we are ready to process this type, we remove it from "collected"
191+ # types and then add it together with all dependent types in the correct
192+ # position.
193+ del all_referenced_types [type_ ]
194+ collect_referenced_types (type_ )
195+ else :
196+ all_referenced_types = TypeSet ()
197+ collect_referenced_types = all_referenced_types .collect_referenced_types
198+
199+ if query :
200+ collect_referenced_types (query )
201+ if mutation :
202+ collect_referenced_types (mutation )
203+ if subscription :
204+ collect_referenced_types (subscription )
205+
206+ for directive in self .directives :
207+ # Directives are not validated until validate_schema() is called.
208+ if is_directive (directive ):
209+ for arg in directive .args .values ():
210+ collect_referenced_types (arg .type )
211+ collect_referenced_types (introspection_types ["__Schema" ])
213212
214- # Keep track of all types referenced within the schema.
213+ # Storing the resulting map for reference by the schema.
215214 type_map : TypeMap = {}
216- # First by deeply visiting all initial types.
217- type_map = reduce (self .type_map_reducer , initial_types , type_map )
218- # Then by deeply visiting all directive types.
219- type_map = reduce (self .type_map_directive_reducer , self .directives , type_map )
220- # Storing the resulting map for reference by the schema
221215 self .type_map = type_map
222216
223217 self ._sub_type_map = {}
224218
225219 # Keep track of all implementations by interface name.
226- self ._implementations = collect_implementations (type_map .values ())
220+ implementations_map : Dict [str , InterfaceImplementations ] = {}
221+ self ._implementations_map = implementations_map
222+
223+ for named_type in all_referenced_types :
224+ if not named_type :
225+ continue
226+
227+ type_name = getattr (named_type , "name" , None )
228+ if type_name in type_map :
229+ raise TypeError (
230+ "Schema must contain uniquely named types"
231+ f" but contains multiple types named '{ type_name } '."
232+ )
233+ type_map [type_name ] = named_type
234+
235+ if is_interface_type (named_type ):
236+ named_type = cast (GraphQLInterfaceType , named_type )
237+ # Store implementations by interface.
238+ for iface in named_type .interfaces :
239+ if is_interface_type (iface ):
240+ iface = cast (GraphQLInterfaceType , iface )
241+ if iface .name in implementations_map :
242+ implementations = implementations_map [iface .name ]
243+ else :
244+ implementations = implementations_map [
245+ iface .name
246+ ] = InterfaceImplementations (objects = [], interfaces = [])
247+
248+ implementations .interfaces .append (named_type )
249+ elif is_object_type (named_type ):
250+ named_type = cast (GraphQLObjectType , named_type )
251+ # Store implementations by objects.
252+ for iface in named_type .interfaces :
253+ if is_interface_type (iface ):
254+ iface = cast (GraphQLInterfaceType , iface )
255+ if iface .name in implementations_map :
256+ implementations = implementations_map [iface .name ]
257+ else :
258+ implementations = implementations_map [
259+ iface .name
260+ ] = InterfaceImplementations (objects = [], interfaces = [])
261+
262+ implementations .objects .append (named_type )
227263
228264 def to_kwargs (self ) -> Dict [str , Any ]:
229265 return dict (
@@ -256,7 +292,9 @@ def get_possible_types(
256292 def get_implementations (
257293 self , interface_type : GraphQLInterfaceType
258294 ) -> InterfaceImplementations :
259- return self ._implementations [interface_type .name ]
295+ return self ._implementations_map .get (
296+ interface_type .name , InterfaceImplementations (objects = [], interfaces = [])
297+ )
260298
261299 def is_possible_type (
262300 self , abstract_type : GraphQLAbstractType , possible_type : GraphQLObjectType
@@ -301,100 +339,43 @@ def get_directive(self, name: str) -> Optional[GraphQLDirective]:
301339 def validation_errors (self ):
302340 return self ._validation_errors
303341
304- def type_map_reducer (
305- self , map_ : TypeMap , type_ : Optional [GraphQLNamedType ] = None
306- ) -> TypeMap :
307- """Reducer function for creating the type map from given types."""
308- if not type_ :
309- return map_
310342
343+ class TypeSet (Dict [GraphQLNamedType , None ]):
344+ """An ordered set of types that can be collected starting from initial types."""
345+
346+ @classmethod
347+ def with_initial_types (cls , types : Collection [GraphQLType ]) -> "TypeSet" :
348+ return cast (TypeSet , super ().fromkeys (types ))
349+
350+ def collect_referenced_types (self , type_ : GraphQLType ) -> None :
351+ """Recursive function supplementing the type starting from an initial type."""
311352 named_type = get_named_type (type_ )
312- try :
313- name = named_type .name
314- except AttributeError :
315- # this is how GraphQL.js handles the case
316- name = None # type: ignore
317-
318- if name in map_ :
319- if map_ [name ] is not named_type :
320- raise TypeError (
321- "Schema must contain uniquely named types but contains multiple"
322- f" types named { name !r} ."
323- )
324- return map_
325- map_ [name ] = named_type
326353
354+ if named_type in self :
355+ return
356+
357+ self [named_type ] = None
358+
359+ collect_referenced_types = self .collect_referenced_types
327360 if is_union_type (named_type ):
328361 named_type = cast (GraphQLUnionType , named_type )
329- map_ = reduce ( self . type_map_reducer , named_type .types , map_ )
330-
362+ for member_type in named_type .types :
363+ collect_referenced_types ( member_type )
331364 elif is_object_type (named_type ) or is_interface_type (named_type ):
332365 named_type = cast (
333366 Union [GraphQLObjectType , GraphQLInterfaceType ], named_type
334367 )
335- map_ = reduce (self .type_map_reducer , named_type .interfaces , map_ )
336- for field in cast (GraphQLInterfaceType , named_type ).fields .values ():
337- types = [arg .type for arg in field .args .values ()]
338- map_ = reduce (self .type_map_reducer , types , map_ )
339- map_ = self .type_map_reducer (map_ , field .type )
368+ for interface_type in named_type .interfaces :
369+ collect_referenced_types (interface_type )
340370
371+ for field in named_type .fields .values ():
372+ collect_referenced_types (field .type )
373+ for arg in field .args .values ():
374+ collect_referenced_types (arg .type )
341375 elif is_input_object_type (named_type ):
342- for field in cast (GraphQLInputObjectType , named_type ).fields .values ():
343- map_ = self .type_map_reducer (map_ , field .type )
344-
345- return map_
346-
347- def type_map_directive_reducer (
348- self , map_ : TypeMap , directive : Optional [GraphQLDirective ] = None
349- ) -> TypeMap :
350- """Reducer function for creating the type map from given directives."""
351- # Directives are not validated until validate_schema() is called.
352- if not is_directive (directive ):
353- return map_ # pragma: no cover
354- directive = cast (GraphQLDirective , directive )
355- return reduce (
356- lambda prev_map , arg : self .type_map_reducer (
357- prev_map , cast (GraphQLNamedType , arg .type )
358- ),
359- directive .args .values (),
360- map_ ,
361- )
362-
363-
364- def collect_implementations (
365- types : Collection [GraphQLNamedType ],
366- ) -> Dict [str , InterfaceImplementations ]:
367- implementations_map : Dict [str , InterfaceImplementations ] = {}
368- for type_ in types :
369- if is_interface_type (type_ ):
370- type_ = cast (GraphQLInterfaceType , type_ )
371- if type_ .name not in implementations_map :
372- implementations_map [type_ .name ] = InterfaceImplementations (
373- objects = [], interfaces = []
374- )
375- # Store implementations by interface.
376- for interface in type_ .interfaces :
377- if is_interface_type (interface ):
378- implementations = implementations_map .get (interface .name )
379- if implementations is None :
380- implementations_map [interface .name ] = InterfaceImplementations (
381- objects = [], interfaces = [type_ ]
382- )
383- else :
384- implementations .interfaces .append (type_ )
385- elif is_object_type (type_ ):
386- type_ = cast (GraphQLObjectType , type_ )
387- # Store implementations by objects.
388- for interface in type_ .interfaces :
389- if is_interface_type (interface ):
390- implementations = implementations_map .get (interface .name )
391- if implementations is None :
392- implementations_map [interface .name ] = InterfaceImplementations (
393- objects = [type_ ], interfaces = []
394- )
395- else :
396- implementations .objects .append (type_ )
397- return implementations_map
376+ named_type = cast (GraphQLInputObjectType , named_type )
377+ for field in named_type .fields .values ():
378+ collect_referenced_types (field .type )
398379
399380
400381def is_schema (schema : Any ) -> bool :
0 commit comments