Skip to content

Commit 24506f3

Browse files
committed
Use typed dictionaries for introspection results (#99)
1 parent e3d6871 commit 24506f3

8 files changed

+392
-151
lines changed

docs/modules/utilities.rst

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ The GraphQL query recommended for a full schema introspection:
1111

1212
.. autofunction:: get_introspection_query
1313

14+
.. autoclass:: IntrospectionQuery
15+
:no-inherited-members:
16+
1417
Get the target Operation from a Document:
1518

1619
.. autofunction:: get_operation_ast

src/graphql/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
# Produce the GraphQL query recommended for a full schema introspection.
165165
# Accepts optional IntrospectionOptions.
166166
get_introspection_query,
167+
IntrospectionQuery,
167168
# Get the target Operation from a Document.
168169
get_operation_ast,
169170
# Get the Type for the target Operation AST.
@@ -700,6 +701,7 @@
700701
"GraphQLSyntaxError",
701702
"located_error",
702703
"get_introspection_query",
704+
"IntrospectionQuery",
703705
"get_operation_ast",
704706
"get_operation_root_type",
705707
"introspection_from_schema",

src/graphql/utilities/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
# Produce the GraphQL query recommended for a full schema introspection.
8-
from .get_introspection_query import get_introspection_query
8+
from .get_introspection_query import get_introspection_query, IntrospectionQuery
99

1010
# Get the target Operation from a Document.
1111
from .get_operation_ast import get_operation_ast
@@ -86,6 +86,7 @@
8686
"BreakingChangeType",
8787
"DangerousChange",
8888
"DangerousChangeType",
89+
"IntrospectionQuery",
8990
"TypeInfo",
9091
"TypeInfoVisitor",
9192
"assert_valid_name",

src/graphql/utilities/build_client_schema.py

+88-48
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from itertools import chain
2-
from typing import cast, Callable, Collection, Dict, List
2+
from typing import cast, Callable, Collection, Dict, List, Union
33

44
from ..language import DirectiveLocation, parse_value
55
from ..pyutils import inspect, Undefined
@@ -31,13 +31,27 @@
3131
is_output_type,
3232
specified_scalar_types,
3333
)
34+
from .get_introspection_query import (
35+
IntrospectionDirective,
36+
IntrospectionEnumType,
37+
IntrospectionField,
38+
IntrospectionInterfaceType,
39+
IntrospectionInputObjectType,
40+
IntrospectionInputValue,
41+
IntrospectionObjectType,
42+
IntrospectionQuery,
43+
IntrospectionScalarType,
44+
IntrospectionType,
45+
IntrospectionTypeRef,
46+
IntrospectionUnionType,
47+
)
3448
from .value_from_ast import value_from_ast
3549

3650
__all__ = ["build_client_schema"]
3751

3852

3953
def build_client_schema(
40-
introspection: Dict, assume_valid: bool = False
54+
introspection: IntrospectionQuery, assume_valid: bool = False
4155
) -> GraphQLSchema:
4256
"""Build a GraphQLSchema for use by client tools.
4357
@@ -64,22 +78,25 @@ def build_client_schema(
6478

6579
# Given a type reference in introspection, return the GraphQLType instance,
6680
# preferring cached instances before building new instances.
67-
def get_type(type_ref: Dict) -> GraphQLType:
81+
def get_type(type_ref: IntrospectionTypeRef) -> GraphQLType:
6882
kind = type_ref.get("kind")
6983
if kind == TypeKind.LIST.name:
7084
item_ref = type_ref.get("ofType")
7185
if not item_ref:
7286
raise TypeError("Decorated type deeper than introspection query.")
87+
item_ref = cast(IntrospectionTypeRef, item_ref)
7388
return GraphQLList(get_type(item_ref))
74-
elif kind == TypeKind.NON_NULL.name:
89+
if kind == TypeKind.NON_NULL.name:
7590
nullable_ref = type_ref.get("ofType")
7691
if not nullable_ref:
7792
raise TypeError("Decorated type deeper than introspection query.")
93+
nullable_ref = cast(IntrospectionTypeRef, nullable_ref)
7894
nullable_type = get_type(nullable_ref)
7995
return GraphQLNonNull(assert_nullable_type(nullable_type))
96+
type_ref = cast(IntrospectionType, type_ref)
8097
return get_named_type(type_ref)
8198

82-
def get_named_type(type_ref: Dict) -> GraphQLNamedType:
99+
def get_named_type(type_ref: IntrospectionType) -> GraphQLNamedType:
83100
type_name = type_ref.get("name")
84101
if not type_name:
85102
raise TypeError(f"Unknown type reference: {inspect(type_ref)}.")
@@ -93,36 +110,42 @@ def get_named_type(type_ref: Dict) -> GraphQLNamedType:
93110
)
94111
return type_
95112

96-
def get_object_type(type_ref: Dict) -> GraphQLObjectType:
113+
def get_object_type(type_ref: IntrospectionObjectType) -> GraphQLObjectType:
97114
return assert_object_type(get_type(type_ref))
98115

99-
def get_interface_type(type_ref: Dict) -> GraphQLInterfaceType:
116+
def get_interface_type(
117+
type_ref: IntrospectionInterfaceType,
118+
) -> GraphQLInterfaceType:
100119
return assert_interface_type(get_type(type_ref))
101120

102121
# Given a type's introspection result, construct the correct GraphQLType instance.
103-
def build_type(type_: Dict) -> GraphQLNamedType:
122+
def build_type(type_: IntrospectionType) -> GraphQLNamedType:
104123
if type_ and "name" in type_ and "kind" in type_:
105-
builder = type_builders.get(cast(str, type_["kind"]))
124+
builder = type_builders.get(type_["kind"])
106125
if builder: # pragma: no cover else
107-
return cast(GraphQLNamedType, builder(type_))
126+
return builder(type_)
108127
raise TypeError(
109128
"Invalid or incomplete introspection result."
110129
" Ensure that a full introspection query is used in order"
111130
f" to build a client schema: {inspect(type_)}."
112131
)
113132

114-
def build_scalar_def(scalar_introspection: Dict) -> GraphQLScalarType:
133+
def build_scalar_def(
134+
scalar_introspection: IntrospectionScalarType,
135+
) -> GraphQLScalarType:
115136
return GraphQLScalarType(
116137
name=scalar_introspection["name"],
117138
description=scalar_introspection.get("description"),
118139
specified_by_url=scalar_introspection.get("specifiedByURL"),
119140
)
120141

121142
def build_implementations_list(
122-
implementing_introspection: Dict,
143+
implementing_introspection: Union[
144+
IntrospectionObjectType, IntrospectionInterfaceType
145+
],
123146
) -> List[GraphQLInterfaceType]:
124-
interfaces = implementing_introspection.get("interfaces")
125-
if interfaces is None:
147+
maybe_interfaces = implementing_introspection.get("interfaces")
148+
if maybe_interfaces is None:
126149
# Temporary workaround until GraphQL ecosystem will fully support
127150
# 'interfaces' on interface types
128151
if implementing_introspection["kind"] == TypeKind.INTERFACE.name:
@@ -131,40 +154,46 @@ def build_implementations_list(
131154
"Introspection result missing interfaces:"
132155
f" {inspect(implementing_introspection)}."
133156
)
157+
interfaces = cast(Collection[IntrospectionInterfaceType], maybe_interfaces)
134158
return [get_interface_type(interface) for interface in interfaces]
135159

136-
def build_object_def(object_introspection: Dict) -> GraphQLObjectType:
160+
def build_object_def(
161+
object_introspection: IntrospectionObjectType,
162+
) -> GraphQLObjectType:
137163
return GraphQLObjectType(
138164
name=object_introspection["name"],
139165
description=object_introspection.get("description"),
140166
interfaces=lambda: build_implementations_list(object_introspection),
141167
fields=lambda: build_field_def_map(object_introspection),
142168
)
143169

144-
def build_interface_def(interface_introspection: Dict) -> GraphQLInterfaceType:
170+
def build_interface_def(
171+
interface_introspection: IntrospectionInterfaceType,
172+
) -> GraphQLInterfaceType:
145173
return GraphQLInterfaceType(
146174
name=interface_introspection["name"],
147175
description=interface_introspection.get("description"),
148176
interfaces=lambda: build_implementations_list(interface_introspection),
149177
fields=lambda: build_field_def_map(interface_introspection),
150178
)
151179

152-
def build_union_def(union_introspection: Dict) -> GraphQLUnionType:
153-
possible_types = union_introspection.get("possibleTypes")
154-
if possible_types is None:
180+
def build_union_def(
181+
union_introspection: IntrospectionUnionType,
182+
) -> GraphQLUnionType:
183+
maybe_possible_types = union_introspection.get("possibleTypes")
184+
if maybe_possible_types is None:
155185
raise TypeError(
156186
"Introspection result missing possibleTypes:"
157187
f" {inspect(union_introspection)}."
158188
)
189+
possible_types = cast(Collection[IntrospectionObjectType], maybe_possible_types)
159190
return GraphQLUnionType(
160191
name=union_introspection["name"],
161192
description=union_introspection.get("description"),
162-
types=lambda: [
163-
get_object_type(type_) for type_ in cast(List[Dict], possible_types)
164-
],
193+
types=lambda: [get_object_type(type_) for type_ in possible_types],
165194
)
166195

167-
def build_enum_def(enum_introspection: Dict) -> GraphQLEnumType:
196+
def build_enum_def(enum_introspection: IntrospectionEnumType) -> GraphQLEnumType:
168197
if enum_introspection.get("enumValues") is None:
169198
raise TypeError(
170199
"Introspection result missing enumValues:"
@@ -184,7 +213,7 @@ def build_enum_def(enum_introspection: Dict) -> GraphQLEnumType:
184213
)
185214

186215
def build_input_object_def(
187-
input_object_introspection: Dict,
216+
input_object_introspection: IntrospectionInputObjectType,
188217
) -> GraphQLInputObjectType:
189218
if input_object_introspection.get("inputFields") is None:
190219
raise TypeError(
@@ -199,16 +228,18 @@ def build_input_object_def(
199228
),
200229
)
201230

202-
type_builders: Dict[str, Callable[[Dict], GraphQLType]] = {
203-
TypeKind.SCALAR.name: build_scalar_def,
204-
TypeKind.OBJECT.name: build_object_def,
205-
TypeKind.INTERFACE.name: build_interface_def,
206-
TypeKind.UNION.name: build_union_def,
207-
TypeKind.ENUM.name: build_enum_def,
208-
TypeKind.INPUT_OBJECT.name: build_input_object_def,
231+
type_builders: Dict[str, Callable[[IntrospectionType], GraphQLNamedType]] = {
232+
TypeKind.SCALAR.name: build_scalar_def, # type: ignore
233+
TypeKind.OBJECT.name: build_object_def, # type: ignore
234+
TypeKind.INTERFACE.name: build_interface_def, # type: ignore
235+
TypeKind.UNION.name: build_union_def, # type: ignore
236+
TypeKind.ENUM.name: build_enum_def, # type: ignore
237+
TypeKind.INPUT_OBJECT.name: build_input_object_def, # type: ignore
209238
}
210239

211-
def build_field_def_map(type_introspection: Dict) -> Dict[str, GraphQLField]:
240+
def build_field_def_map(
241+
type_introspection: Union[IntrospectionObjectType, IntrospectionInterfaceType],
242+
) -> Dict[str, GraphQLField]:
212243
if type_introspection.get("fields") is None:
213244
raise TypeError(
214245
f"Introspection result missing fields: {type_introspection}."
@@ -218,8 +249,9 @@ def build_field_def_map(type_introspection: Dict) -> Dict[str, GraphQLField]:
218249
for field_introspection in type_introspection["fields"]
219250
}
220251

221-
def build_field(field_introspection: Dict) -> GraphQLField:
222-
type_ = get_type(field_introspection["type"])
252+
def build_field(field_introspection: IntrospectionField) -> GraphQLField:
253+
type_introspection = cast(IntrospectionType, field_introspection["type"])
254+
type_ = get_type(type_introspection)
223255
if not is_output_type(type_):
224256
raise TypeError(
225257
"Introspection must provide output type for fields,"
@@ -242,27 +274,30 @@ def build_field(field_introspection: Dict) -> GraphQLField:
242274
)
243275

244276
def build_argument_def_map(
245-
input_value_introspections: Dict,
277+
argument_value_introspections: Collection[IntrospectionInputValue],
246278
) -> Dict[str, GraphQLArgument]:
247279
return {
248280
argument_introspection["name"]: build_argument(argument_introspection)
249-
for argument_introspection in input_value_introspections
281+
for argument_introspection in argument_value_introspections
250282
}
251283

252-
def build_argument(argument_introspection: Dict) -> GraphQLArgument:
253-
type_ = get_type(argument_introspection["type"])
284+
def build_argument(
285+
argument_introspection: IntrospectionInputValue,
286+
) -> GraphQLArgument:
287+
type_introspection = cast(IntrospectionType, argument_introspection["type"])
288+
type_ = get_type(type_introspection)
254289
if not is_input_type(type_):
255290
raise TypeError(
256291
"Introspection must provide input type for arguments,"
257292
f" but received: {inspect(type_)}."
258293
)
259294
type_ = cast(GraphQLInputType, type_)
260295

261-
default_value = argument_introspection.get("defaultValue")
296+
default_value_introspection = argument_introspection.get("defaultValue")
262297
default_value = (
263298
Undefined
264-
if default_value is None
265-
else value_from_ast(parse_value(default_value), type_)
299+
if default_value_introspection is None
300+
else value_from_ast(parse_value(default_value_introspection), type_)
266301
)
267302
return GraphQLArgument(
268303
type_,
@@ -272,7 +307,7 @@ def build_argument(argument_introspection: Dict) -> GraphQLArgument:
272307
)
273308

274309
def build_input_value_def_map(
275-
input_value_introspections: Dict,
310+
input_value_introspections: Collection[IntrospectionInputValue],
276311
) -> Dict[str, GraphQLInputField]:
277312
return {
278313
input_value_introspection["name"]: build_input_value(
@@ -281,20 +316,23 @@ def build_input_value_def_map(
281316
for input_value_introspection in input_value_introspections
282317
}
283318

284-
def build_input_value(input_value_introspection: Dict) -> GraphQLInputField:
285-
type_ = get_type(input_value_introspection["type"])
319+
def build_input_value(
320+
input_value_introspection: IntrospectionInputValue,
321+
) -> GraphQLInputField:
322+
type_introspection = cast(IntrospectionType, input_value_introspection["type"])
323+
type_ = get_type(type_introspection)
286324
if not is_input_type(type_):
287325
raise TypeError(
288326
"Introspection must provide input type for input fields,"
289327
f" but received: {inspect(type_)}."
290328
)
291329
type_ = cast(GraphQLInputType, type_)
292330

293-
default_value = input_value_introspection.get("defaultValue")
331+
default_value_introspection = input_value_introspection.get("defaultValue")
294332
default_value = (
295333
Undefined
296-
if default_value is None
297-
else value_from_ast(parse_value(default_value), type_)
334+
if default_value_introspection is None
335+
else value_from_ast(parse_value(default_value_introspection), type_)
298336
)
299337
return GraphQLInputField(
300338
type_,
@@ -303,7 +341,9 @@ def build_input_value(input_value_introspection: Dict) -> GraphQLInputField:
303341
deprecation_reason=input_value_introspection.get("deprecationReason"),
304342
)
305343

306-
def build_directive(directive_introspection: Dict) -> GraphQLDirective:
344+
def build_directive(
345+
directive_introspection: IntrospectionDirective,
346+
) -> GraphQLDirective:
307347
if directive_introspection.get("args") is None:
308348
raise TypeError(
309349
"Introspection result missing directive args:"

0 commit comments

Comments
 (0)