1
- from functools import reduce
2
1
from typing import (
3
2
Any ,
4
3
Collection ,
25
24
get_named_type ,
26
25
is_input_object_type ,
27
26
is_interface_type ,
28
- is_named_type ,
29
27
is_object_type ,
30
28
is_union_type ,
31
29
)
@@ -100,7 +98,7 @@ class GraphQLSchema:
100
98
ast_node : Optional [ast .SchemaDefinitionNode ]
101
99
extension_ast_nodes : Optional [FrozenList [ast .SchemaExtensionNode ]]
102
100
103
- _implementations : Dict [str , InterfaceImplementations ]
101
+ _implementations_map : Dict [str , InterfaceImplementations ]
104
102
_sub_type_map : Dict [str , Set [str ]]
105
103
_validation_errors : Optional [List [GraphQLError ]]
106
104
@@ -119,18 +117,12 @@ def __init__(
119
117
"""Initialize GraphQL schema.
120
118
121
119
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.
125
121
"""
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.
128
122
self ._validation_errors = [] if assume_valid else None
129
123
130
124
# 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.
134
126
if query and not isinstance (query , GraphQLType ):
135
127
raise TypeError ("Expected query to be a GraphQL type." )
136
128
if mutation and not isinstance (mutation , GraphQLType ):
@@ -140,28 +132,16 @@ def __init__(
140
132
if types is None :
141
133
types = []
142
134
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
148
137
):
149
138
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."
152
140
)
153
141
if directives is not None :
154
142
# 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." )
165
145
if not isinstance (directives , FrozenList ):
166
146
directives = FrozenList (directives )
167
147
if extensions is not None and (
@@ -201,29 +181,85 @@ def __init__(
201
181
else cast (FrozenList [GraphQLDirective ], directives )
202
182
)
203
183
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.
211
186
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" ])
213
212
214
- # Keep track of all types referenced within the schema.
213
+ # Storing the resulting map for reference by the schema.
215
214
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
221
215
self .type_map = type_map
222
216
223
217
self ._sub_type_map = {}
224
218
225
219
# 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 )
227
263
228
264
def to_kwargs (self ) -> Dict [str , Any ]:
229
265
return dict (
@@ -256,7 +292,9 @@ def get_possible_types(
256
292
def get_implementations (
257
293
self , interface_type : GraphQLInterfaceType
258
294
) -> InterfaceImplementations :
259
- return self ._implementations [interface_type .name ]
295
+ return self ._implementations_map .get (
296
+ interface_type .name , InterfaceImplementations (objects = [], interfaces = [])
297
+ )
260
298
261
299
def is_possible_type (
262
300
self , abstract_type : GraphQLAbstractType , possible_type : GraphQLObjectType
@@ -301,100 +339,43 @@ def get_directive(self, name: str) -> Optional[GraphQLDirective]:
301
339
def validation_errors (self ):
302
340
return self ._validation_errors
303
341
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_
310
342
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."""
311
352
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
326
353
354
+ if named_type in self :
355
+ return
356
+
357
+ self [named_type ] = None
358
+
359
+ collect_referenced_types = self .collect_referenced_types
327
360
if is_union_type (named_type ):
328
361
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 )
331
364
elif is_object_type (named_type ) or is_interface_type (named_type ):
332
365
named_type = cast (
333
366
Union [GraphQLObjectType , GraphQLInterfaceType ], named_type
334
367
)
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 )
340
370
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 )
341
375
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 )
398
379
399
380
400
381
def is_schema (schema : Any ) -> bool :
0 commit comments