Skip to content

Commit e31a703

Browse files
committed
Implement build_client_schema utility #5
* Implement unit tests for `build_client_schema`. * Implement `__eq__` methods on `GraphQLEnumValue`, `ExecutionResult`.
1 parent f375618 commit e31a703

File tree

8 files changed

+724
-16
lines changed

8 files changed

+724
-16
lines changed

graphql/core/execution/base.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ def __init__(self, data=None, errors=None, invalid=False):
9494

9595
self.invalid = invalid
9696

97+
def __eq__(self, other):
98+
return (
99+
self is other or (
100+
isinstance(other, ExecutionResult) and
101+
self.data == other.data and
102+
self.errors == other.errors and
103+
self.invalid == other.invalid
104+
)
105+
)
106+
97107

98108
def get_operation_root_type(schema, operation):
99109
op = operation.operation

graphql/core/type/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
GraphQLInputObjectField,
1313
GraphQLList,
1414
GraphQLNonNull,
15+
is_abstract_type,
16+
is_composite_type,
1517
is_input_type,
18+
is_leaf_type,
19+
is_output_type
1620
)
1721
from .scalars import ( # no import order
1822
GraphQLInt,

graphql/core/type/definition.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,12 +556,23 @@ def define_enum_values(type, value_map):
556556
class GraphQLEnumValue(object):
557557
__slots__ = 'name', 'value', 'deprecation_reason', 'description'
558558

559-
def __init__(self, value=None, deprecation_reason=None, description=None):
560-
self.name = None
559+
def __init__(self, value=None, deprecation_reason=None, description=None, name=None):
560+
self.name = name
561561
self.value = value
562562
self.deprecation_reason = deprecation_reason
563563
self.description = description
564564

565+
def __eq__(self, other):
566+
return (
567+
self is other or (
568+
isinstance(other, GraphQLEnumValue) and
569+
self.name == other.name and
570+
self.value == other.value and
571+
self.deprecation_reason == other.deprecation_reason and
572+
self.description == other.description
573+
)
574+
)
575+
565576

566577
class GraphQLInputObjectType(GraphQLType):
567578
"""Input Object Type Definition

graphql/core/type/introspection.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@
7171

7272

7373
class TypeKind(object):
74-
SCALAR = 0
75-
OBJECT = 1
76-
INTERFACE = 2
77-
UNION = 3
78-
ENUM = 4
79-
INPUT_OBJECT = 5
80-
LIST = 6
81-
NON_NULL = 7
74+
SCALAR = 'SCALAR'
75+
OBJECT = 'OBJECT'
76+
INTERFACE = 'INTERFACE'
77+
UNION = 'UNION'
78+
ENUM = 'ENUM'
79+
INPUT_OBJECT = 'INPUT_OBJECT'
80+
LIST = 'LIST'
81+
NON_NULL = 'NON_NULL'
8282

8383

8484
class TypeFieldResolvers(object):
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
from collections import OrderedDict
2+
from graphql.core.type.definition import get_named_type
3+
from graphql.core.type import (
4+
GraphQLArgument,
5+
GraphQLBoolean,
6+
GraphQLEnumType,
7+
GraphQLEnumValue,
8+
GraphQLField,
9+
GraphQLFloat,
10+
GraphQLID,
11+
GraphQLInputObjectType,
12+
GraphQLInputObjectField,
13+
GraphQLInt,
14+
GraphQLInterfaceType,
15+
GraphQLList,
16+
GraphQLNonNull,
17+
GraphQLObjectType,
18+
GraphQLScalarType,
19+
GraphQLSchema,
20+
GraphQLString,
21+
GraphQLUnionType,
22+
is_input_type,
23+
is_output_type,
24+
)
25+
from graphql.core.type.introspection import TypeKind
26+
from ..language.parser import parse_value
27+
from .value_from_ast import value_from_ast
28+
29+
_none = lambda *_: None
30+
_false = lambda *_: False
31+
32+
33+
def no_execution(*args):
34+
raise Exception('Client Schema cannot be used for execution.')
35+
36+
37+
def build_client_schema(introspection):
38+
schema_introspection = introspection['__schema']
39+
40+
type_introspection_map = {t['name']: t for t in schema_introspection['types']}
41+
42+
type_def_cache = {
43+
'String': GraphQLString,
44+
'Int': GraphQLInt,
45+
'Float': GraphQLFloat,
46+
'Boolean': GraphQLBoolean,
47+
'ID': GraphQLID
48+
}
49+
50+
def get_type(type_ref):
51+
kind = type_ref.get('kind')
52+
53+
if kind == TypeKind.LIST:
54+
item_ref = type_ref.get('ofType')
55+
56+
if not item_ref:
57+
raise Exception('Decorated type deeper than introspection query.')
58+
59+
return GraphQLList(get_type(item_ref))
60+
61+
elif kind == TypeKind.NON_NULL:
62+
nullable_ref = type_ref.get('ofType')
63+
if not nullable_ref:
64+
raise Exception('Decorated type deeper than introspection query.')
65+
66+
return GraphQLNonNull(get_type(nullable_ref))
67+
68+
return get_named_type(type_ref['name'])
69+
70+
def get_named_type(type_name):
71+
if type_name in type_def_cache:
72+
return type_def_cache[type_name]
73+
74+
type_introspection = type_introspection_map.get(type_name)
75+
if not type_introspection:
76+
raise Exception(
77+
'Invalid or incomplete schema, unknown type: {}. Ensure that a full introspection query '
78+
'is used in order to build a client schema.'.format(type_name)
79+
)
80+
81+
type_def = type_def_cache[type_name] = build_type(type_introspection)
82+
return type_def
83+
84+
def get_input_type(type_ref):
85+
input_type = get_type(type_ref)
86+
assert is_input_type(input_type), 'Introspection must provide input type for arguments.'
87+
return input_type
88+
89+
def get_output_type(type_ref):
90+
output_type = get_type(type_ref)
91+
assert is_output_type(output_type), 'Introspection must provide output type for fields.'
92+
return output_type
93+
94+
def get_object_type(type_ref):
95+
object_type = get_type(type_ref)
96+
assert isinstance(object_type, GraphQLObjectType), 'Introspection must provide object type for possibleTypes.'
97+
return object_type
98+
99+
def get_interface_type(type_ref):
100+
interface_type = get_type(type_ref)
101+
assert isinstance(interface_type, GraphQLInterfaceType), 'Introspection must provide interface type for interfaces.'
102+
return interface_type
103+
104+
def build_type(type):
105+
type_kind = type.get('kind')
106+
handler = type_builders.get(type_kind)
107+
if not handler:
108+
raise Exception(
109+
'Invalid or incomplete schema, unknown kind: {}. Ensure that a full introspection query '
110+
'is used in order to build a client schema.'.format(type_kind)
111+
)
112+
113+
return handler(type)
114+
115+
def build_scalar_def(scalar_introspection):
116+
return GraphQLScalarType(
117+
name=scalar_introspection['name'],
118+
description=scalar_introspection['description'],
119+
serialize=_none,
120+
parse_value=_false,
121+
parse_literal=_false
122+
)
123+
124+
def build_object_def(object_introspection):
125+
return GraphQLObjectType(
126+
name=object_introspection['name'],
127+
description=object_introspection['description'],
128+
interfaces=[get_interface_type(i) for i in object_introspection['interfaces']],
129+
fields=lambda: build_field_def_map(object_introspection)
130+
)
131+
132+
def build_interface_def(interface_introspection):
133+
return GraphQLInterfaceType(
134+
name=interface_introspection['name'],
135+
description=interface_introspection['description'],
136+
fields=lambda: build_field_def_map(interface_introspection),
137+
resolve_type=no_execution
138+
)
139+
140+
def build_union_def(union_introspection):
141+
return GraphQLUnionType(
142+
name=union_introspection['name'],
143+
description=union_introspection['description'],
144+
types=[get_object_type(t) for t in union_introspection['possibleTypes']],
145+
resolve_type=no_execution
146+
)
147+
148+
def build_enum_def(enum_introspection):
149+
return GraphQLEnumType(
150+
name=enum_introspection['name'],
151+
description=enum_introspection['description'],
152+
values=OrderedDict([(value_introspection['name'],
153+
GraphQLEnumValue(description=value_introspection['description']))
154+
for value_introspection in enum_introspection['enumValues']
155+
])
156+
)
157+
158+
def build_input_object_def(input_object_introspection):
159+
return GraphQLInputObjectType(
160+
name=input_object_introspection['name'],
161+
description=input_object_introspection['description'],
162+
fields=lambda: build_input_value_def_map(input_object_introspection['inputFields'],
163+
GraphQLInputObjectField)
164+
)
165+
166+
type_builders = {
167+
TypeKind.SCALAR: build_scalar_def,
168+
TypeKind.OBJECT: build_object_def,
169+
TypeKind.INTERFACE: build_interface_def,
170+
TypeKind.UNION: build_union_def,
171+
TypeKind.ENUM: build_enum_def,
172+
TypeKind.INPUT_OBJECT: build_input_object_def
173+
}
174+
175+
def build_field_def_map(type_introspection):
176+
return OrderedDict([
177+
(f['name'], GraphQLField(
178+
type=get_output_type(f['type']),
179+
description=f['description'],
180+
resolver=no_execution,
181+
args=build_input_value_def_map(f['args'])))
182+
for f in type_introspection['fields']
183+
])
184+
185+
def build_default_value(f):
186+
default_value = f.get('defaultValue')
187+
if default_value is None:
188+
return None
189+
190+
return value_from_ast(parse_value(default_value), get_input_type(f['type']))
191+
192+
def build_input_value_def_map(input_value_introspection, argument_type=GraphQLArgument):
193+
return OrderedDict([
194+
(f['name'], argument_type(
195+
description=f['description'],
196+
type=get_input_type(f['type']),
197+
default_value=build_default_value(f)
198+
)) for f in input_value_introspection
199+
])
200+
201+
for type_introspection_name in type_introspection_map:
202+
get_named_type(type_introspection_name)
203+
204+
query_type = get_type(schema_introspection['queryType'])
205+
mutation_type = get_type(schema_introspection['mutationType']) if schema_introspection.get('mutationType') else None
206+
return GraphQLSchema(query_type, mutation_type)

tests/core_execution/test_nonnull.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ def nonNullSync(self):
4949
def promise(self):
5050
return succeed(None)
5151

52-
def non_null_promise(self):
53-
return succeed(None)
54-
5552
def nest(self):
5653
return NullingData()
5754

@@ -95,9 +92,6 @@ def check(doc, data, expected):
9592
'data': response.data
9693
}
9794

98-
import pprint
99-
pprint.pprint(result)
100-
10195
assert result == expected
10296

10397

tests/core_utils/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)