Skip to content

Commit c957874

Browse files
committed
[RFC] Add Schema Definition to IDL.
Related GraphQL-js commit: graphql/graphql-js@8379e71
1 parent 10879e4 commit c957874

File tree

11 files changed

+524
-71
lines changed

11 files changed

+524
-71
lines changed

graphql/language/ast.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -834,8 +834,80 @@ def __hash__(self):
834834
return id(self)
835835

836836

837+
# Type System Definition
838+
837839
class TypeDefinition(Node):
838-
__slots__ = ()
840+
pass
841+
842+
843+
class TypeSystemDefinition(TypeDefinition):
844+
pass
845+
846+
847+
class SchemaDefinition(TypeSystemDefinition):
848+
__slots__ = ('loc', 'operation_types',)
849+
_fields = ('operation_types',)
850+
851+
def __init__(self, operation_types, loc=None):
852+
self.operation_types = operation_types
853+
self.loc = loc
854+
855+
def __eq__(self, other):
856+
return (
857+
self is other or (
858+
isinstance(other, SchemaDefinition) and
859+
self.operation_types == other.operation_types
860+
)
861+
)
862+
863+
def __repr__(self):
864+
return ('SchemaDefinition('
865+
'operation_types={self.operation_types!r}'
866+
')').format(self=self)
867+
868+
def __copy__(self):
869+
return type(self)(
870+
self.operation_types,
871+
self.loc
872+
)
873+
874+
def __hash__(self):
875+
return id(self)
876+
877+
878+
class OperationTypeDefinition(Node):
879+
__slots__ = ('loc', 'operation', 'type',)
880+
_fields = ('operation', 'type',)
881+
882+
def __init__(self, operation, type, loc=None):
883+
self.operation = operation
884+
self.type = type
885+
self.loc = loc
886+
887+
def __eq__(self, other):
888+
return (
889+
self is other or (
890+
isinstance(other, OperationTypeDefinition) and
891+
self.operation == other.operation and
892+
self.type == other.type
893+
)
894+
)
895+
896+
def __repr__(self):
897+
return ('OperationTypeDefinition('
898+
'operation={self.operation!r}'
899+
', type={self.type!r}'
900+
')').format(self=self)
901+
902+
def __copy__(self):
903+
return type(self)(
904+
self.operation,
905+
self.type,
906+
self.loc
907+
)
908+
909+
def __hash__(self):
910+
return id(self)
839911

840912

841913
class ObjectTypeDefinition(TypeDefinition):
@@ -1166,7 +1238,7 @@ def __hash__(self):
11661238
return id(self)
11671239

11681240

1169-
class TypeExtensionDefinition(TypeDefinition):
1241+
class TypeExtensionDefinition(TypeSystemDefinition):
11701242
__slots__ = ('loc', 'definition',)
11711243
_fields = ('definition',)
11721244

@@ -1198,7 +1270,7 @@ def __hash__(self):
11981270
return id(self)
11991271

12001272

1201-
class DirectiveDefinition(TypeDefinition):
1273+
class DirectiveDefinition(TypeSystemDefinition):
12021274
__slots__ = ('loc', 'name', 'arguments', 'locations')
12031275
_fields = ('name', 'locations')
12041276

graphql/language/parser.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def parse_definition(parser):
209209
return parse_operation_definition(parser)
210210
elif name == 'fragment':
211211
return parse_fragment_definition(parser)
212-
elif name in ('scalar', 'type', 'interface', 'union', 'enum', 'input', 'extend', 'directive'):
212+
elif name in ('schema', 'scalar', 'type', 'interface', 'union', 'enum', 'input', 'extend', 'directive'):
213213
return parse_type_system_definition(parser)
214214

215215
raise unexpected(parser)
@@ -228,8 +228,7 @@ def parse_operation_definition(parser):
228228
loc=loc(parser, start)
229229
)
230230

231-
operation_token = expect(parser, TokenKind.NAME)
232-
operation = operation_token.value
231+
operation = parse_operation_type(parser)
233232

234233
name = None
235234
if peek(parser, TokenKind.NAME):
@@ -245,6 +244,19 @@ def parse_operation_definition(parser):
245244
)
246245

247246

247+
def parse_operation_type(parser):
248+
operation_token = expect(parser, TokenKind.NAME)
249+
operation = operation_token.value
250+
if operation == 'query':
251+
return 'query'
252+
elif operation == 'mutation':
253+
return 'mutation'
254+
elif operation == 'subscription':
255+
return 'subscription'
256+
257+
raise unexpected(parser, operation_token)
258+
259+
248260
def parse_variable_definitions(parser):
249261
if peek(parser, TokenKind.PAREN_L):
250262
return many(
@@ -512,7 +524,10 @@ def parse_type_system_definition(parser):
512524

513525
name = parser.token.value
514526

515-
if name == 'scalar':
527+
if name == 'schema':
528+
return parse_schema_definition(parser)
529+
530+
elif name == 'scalar':
516531
return parse_scalar_type_definition(parser)
517532

518533
elif name == 'type':
@@ -539,6 +554,34 @@ def parse_type_system_definition(parser):
539554
raise unexpected(parser)
540555

541556

557+
def parse_schema_definition(parser):
558+
start = parser.token.start
559+
expect_keyword(parser, 'schema')
560+
operation_types = many(
561+
parser,
562+
TokenKind.BRACE_L,
563+
parse_operation_type_definition,
564+
TokenKind.BRACE_R
565+
)
566+
567+
return ast.SchemaDefinition(
568+
operation_types=operation_types,
569+
loc=loc(parser, start)
570+
)
571+
572+
573+
def parse_operation_type_definition(parser):
574+
start = parser.token.start
575+
operation = parse_operation_type(parser)
576+
expect(parser, TokenKind.COLON)
577+
578+
return ast.OperationTypeDefinition(
579+
operation=operation,
580+
type=parse_named_type(parser),
581+
loc=loc(parser, start)
582+
)
583+
584+
542585
def parse_scalar_type_definition(parser):
543586
start = parser.token.start
544587
expect_keyword(parser, 'scalar')

graphql/language/printer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ def leave_NonNullType(self, node, *args):
111111

112112
# Type Definitions:
113113

114+
def leave_SchemaDefinition(self, node, *args):
115+
return 'schema ' + block(node.operation_types)
116+
117+
def leave_OperationTypeDefinition(self, node, *args):
118+
return '{}: {}'.format(node.operation, node.type)
119+
114120
def leave_ScalarTypeDefinition(self, node, *args):
115121
return 'scalar ' + node.name
116122

graphql/language/tests/fixtures.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
# LICENSE file in the root directory of this source tree. An additional grant
6868
# of patent rights can be found in the PATENTS file in the same directory.
6969
70+
schema {
71+
query: QueryType
72+
mutation: MutationType
73+
}
74+
7075
type Foo implements Bar {
7176
one: Type
7277
two(argument: InputType!): Type

graphql/language/tests/test_schema_printer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ def test_prints_kitchen_sink():
3636
ast = parse(SCHEMA_KITCHEN_SINK)
3737
printed = print_ast(ast)
3838

39-
expected = '''type Foo implements Bar {
39+
expected = '''schema {
40+
query: QueryType
41+
mutation: MutationType
42+
}
43+
44+
type Foo implements Bar {
4045
one: Type
4146
two(argument: InputType!): Type
4247
three(argument: InputType, other: String): Int

graphql/language/visitor_meta.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,21 @@
3030
ast.ListType: ('type',),
3131
ast.NonNullType: ('type',),
3232

33+
ast.SchemaDefinition: ('operation_types',),
34+
ast.OperationTypeDefinition: ('type',),
35+
36+
ast.ScalarTypeDefinition: ('name',),
3337
ast.ObjectTypeDefinition: ('name', 'interfaces', 'fields'),
3438
ast.FieldDefinition: ('name', 'arguments', 'type'),
3539
ast.InputValueDefinition: ('name', 'type', 'default_value'),
3640
ast.InterfaceTypeDefinition: ('name', 'fields'),
3741
ast.UnionTypeDefinition: ('name', 'types'),
38-
ast.ScalarTypeDefinition: ('name',),
3942
ast.EnumTypeDefinition: ('name', 'values'),
4043
ast.EnumValueDefinition: ('name',),
4144
ast.InputObjectTypeDefinition: ('name', 'fields'),
45+
4246
ast.TypeExtensionDefinition: ('definition',),
47+
4348
ast.DirectiveDefinition: ('name', 'arguments', 'locations'),
4449
}
4550

graphql/utils/build_ast_schema.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,39 +34,35 @@ def _false(*_): return False
3434
def _none(*_): return None
3535

3636

37-
def build_ast_schema(document, query_type_name, mutation_type_name=None, subscription_type_name=None):
37+
def build_ast_schema(document):
3838
assert isinstance(document, ast.Document), 'must pass in Document ast.'
39-
assert query_type_name, 'must pass in query type'
39+
40+
schema_def = None
4041

4142
type_asts = (
43+
ast.ScalarTypeDefinition,
4244
ast.ObjectTypeDefinition,
4345
ast.InterfaceTypeDefinition,
4446
ast.EnumTypeDefinition,
4547
ast.UnionTypeDefinition,
46-
ast.ScalarTypeDefinition,
4748
ast.InputObjectTypeDefinition,
4849
)
4950

5051
type_defs = []
5152
directive_defs = []
5253

5354
for d in document.definitions:
55+
if isinstance(d, ast.SchemaDefinition):
56+
if schema_def:
57+
raise Exception('Must provide only one schema definition.')
58+
schema_def = d
5459
if isinstance(d, type_asts):
5560
type_defs.append(d)
5661
elif isinstance(d, ast.DirectiveDefinition):
5762
directive_defs.append(d)
5863

5964
ast_map = {d.name.value: d for d in type_defs}
6065

61-
if query_type_name not in ast_map:
62-
raise Exception('Specified query type {} not found in document.'.format(query_type_name))
63-
64-
if mutation_type_name and mutation_type_name not in ast_map:
65-
raise Exception('Specified mutation type {} not found in document.'.format(mutation_type_name))
66-
67-
if subscription_type_name and subscription_type_name not in ast_map:
68-
raise Exception('Specified subscription type {} not found in document.'.format(subscription_type_name))
69-
7066
inner_type_map = OrderedDict([
7167
('String', GraphQLString),
7268
('Int', GraphQLInt),
@@ -81,11 +77,11 @@ def produce_type_def(type_ast):
8177
return _build_wrapped_type(inner_type_map[type_name], type_ast)
8278

8379
if type_name not in ast_map:
84-
raise Exception('Type {} not found in document.'.format(type_name))
80+
raise Exception('Type "{}" not found in document.'.format(type_name))
8581

8682
inner_type_def = make_schema_def(ast_map[type_name])
8783
if not inner_type_def:
88-
raise Exception('Nothing constructed for {}.'.format(type_name))
84+
raise Exception('Nothing constructed for "{}".'.format(type_name))
8985

9086
inner_type_map[type_name] = inner_type_def
9187
return _build_wrapped_type(inner_type_def, type_ast)
@@ -172,7 +168,7 @@ def make_schema_def(definition):
172168

173169
handler = _schema_def_handlers.get(type(definition))
174170
if not handler:
175-
raise Exception('{} not supported.'.format(type(definition).__name__))
171+
raise Exception('Type kind "{}" not supported.'.format(type(definition).__name__))
176172

177173
return handler(definition)
178174

@@ -186,6 +182,33 @@ def get_directive(directive_ast):
186182
for definition in type_defs:
187183
produce_type_def(definition)
188184

185+
if not schema_def:
186+
raise Exception('Must provide a schema definition.')
187+
188+
query_type_name = None
189+
mutation_type_name = None
190+
subscription_type_name = None
191+
for operation_type in schema_def.operation_types:
192+
type_name = operation_type.type.name.value
193+
if operation_type.operation == 'query':
194+
query_type_name = type_name
195+
elif operation_type.operation == 'mutation':
196+
mutation_type_name = type_name
197+
elif operation_type.operation == 'subscription':
198+
subscription_type_name = type_name
199+
200+
if not query_type_name:
201+
raise Exception('Must provide schema definition with query type.')
202+
203+
if query_type_name not in ast_map:
204+
raise Exception('Specified query type "{}" not found in document.'.format(query_type_name))
205+
206+
if mutation_type_name and mutation_type_name not in ast_map:
207+
raise Exception('Specified mutation type "{}" not found in document.'.format(mutation_type_name))
208+
209+
if subscription_type_name and subscription_type_name not in ast_map:
210+
raise Exception('Specified subscription type "{}" not found in document.'.format(subscription_type_name))
211+
189212
schema_kwargs = {'query': produce_type_def(ast_map[query_type_name])}
190213

191214
if mutation_type_name:

graphql/utils/schema_printer.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def _is_builtin_scalar(typename):
3434

3535
def _print_filtered_schema(schema, directive_filter, type_filter):
3636
return '\n\n'.join([
37+
_print_schema_definition(schema)
38+
] + [
3739
_print_directive(directive)
3840
for directive in schema.get_directives()
3941
if directive_filter(directive.name)
@@ -44,6 +46,24 @@ def _print_filtered_schema(schema, directive_filter, type_filter):
4446
]) + '\n'
4547

4648

49+
def _print_schema_definition(schema):
50+
operation_types = []
51+
52+
query_type = schema.get_query_type()
53+
if query_type:
54+
operation_types.append(' query: {}'.format(query_type))
55+
56+
mutation_type = schema.get_mutation_type()
57+
if mutation_type:
58+
operation_types.append(' mutation: {}'.format(mutation_type))
59+
60+
subscription_type = schema.get_subscription_type()
61+
if subscription_type:
62+
operation_types.append(' subscription: {}'.format(subscription_type))
63+
64+
return 'schema {{\n{}\n}}'.format('\n'.join(operation_types))
65+
66+
4767
def _print_type(type):
4868
if isinstance(type, GraphQLScalarType):
4969
return _print_scalar(type)

0 commit comments

Comments
 (0)