Skip to content

Commit d8fe99d

Browse files
committed
AST Printer
1 parent acb7186 commit d8fe99d

File tree

3 files changed

+194
-5
lines changed

3 files changed

+194
-5
lines changed

graphql/core/executor/values.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
import collections
22
from ..error import GraphQLError
33
from ..language import ast
4+
from ..language.printer import print_ast
45
from ..type import (GraphQLNonNull, GraphQLList, GraphQLInputObjectType,
56
GraphQLScalarType, GraphQLEnumType, is_input_type)
67
from ..utils import type_from_ast, is_nullish
78

89
__all__ = ['get_variable_values', 'get_argument_values']
910

1011

11-
def print_ast(ast):
12-
# TODO: replace to real printer
13-
return repr(ast)
14-
15-
1612
def get_variable_values(schema, definition_asts, inputs):
1713
"""Prepares an object map of variables of the correct type based on the provided variable definitions and arbitrary input.
1814
If the input cannot be coerced to match the variable definitions, a GraphQLError will be thrown."""

graphql/core/language/printer.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import json
2+
from .visitor import visit, Visitor
3+
4+
__all__ = ['print_ast']
5+
6+
7+
def print_ast(ast):
8+
return visit(ast, PrintingVisitor())
9+
10+
11+
class PrintingVisitor(Visitor):
12+
def leave_Name(self, node, *args):
13+
return node.value
14+
15+
def leave_Variable(self, node, *args):
16+
return '$' + node.name
17+
18+
def leave_Document(self, node, *args):
19+
return join(node.definitions, '\n\n') + '\n'
20+
21+
def leave_OperationDefinition(self, node, *args):
22+
name = node.name
23+
selection_set = node.selection_set
24+
if not name:
25+
return selection_set
26+
op = node.operation
27+
defs = wrap('(', join(node.variable_definitions, ', '), ')')
28+
directives = join(node.directives, ' ')
29+
return join([op, join([name, defs]), directives, selection_set], ' ')
30+
31+
def leave_VariableDefinition(self, node, *args):
32+
return node.variable + ': ' + node.type + wrap(' = ', node.default_value)
33+
34+
def leave_SelectionSet(self, node, *args):
35+
return block(node.selections)
36+
37+
def leave_Field(self, node, *args):
38+
return join([
39+
wrap('', node.alias, ': ') + node.name + wrap('(', join(node.arguments, ', '), ')'),
40+
join(node.directives, ' '),
41+
node.selection_set
42+
], ' ')
43+
44+
def leave_Argument(self, node, *args):
45+
return node.name + ': ' + node.value
46+
47+
# Fragments
48+
49+
def leave_FragmentSpread(self, node, *args):
50+
return '...' + node.name + wrap(' ', join(node.directives, ' '))
51+
52+
def leave_InlineFragment(self, node, *args):
53+
return ('... on ' + node.type_condition + ' ' +
54+
wrap('', join(node.directives, ' '), ' ') +
55+
node.selection_set)
56+
57+
def leave_FragmentDefinition(self, node, *args):
58+
return ('fragment {} on {} '.format(node.name, node.type_condition) +
59+
wrap('', join(node.directives, ' '), ' ') +
60+
node.selection_set)
61+
62+
# Value
63+
64+
def leave_IntValue(self, node, *args):
65+
return node.value
66+
67+
def leave_FloatValue(self, node, *args):
68+
return node.value
69+
70+
def leave_StringValue(self, node, *args):
71+
return json.dumps(node.value)
72+
73+
def leave_BooleanValue(self, node, *args):
74+
return json.dumps(node.value)
75+
76+
def leave_EnumValue(self, node, *args):
77+
return node.value
78+
79+
def leave_ListValue(self, node, *args):
80+
return '[' + join(node.values, ', ') + ']'
81+
82+
def leave_ObjectValue(self, node, *args):
83+
return '{' + join(node.fields, ', ') + '}'
84+
85+
def leave_ObjectField(self, node, *args):
86+
return node.name + ': ' + node.value
87+
88+
# Directive
89+
90+
def leave_Directive(self, node, *args):
91+
return '@' + node.name + wrap('(', join(node.arguments, ', '), ')')
92+
93+
# Type
94+
95+
def leave_NamedType(self, node, *args):
96+
return node.name
97+
98+
def leave_ListType(self, node, *args):
99+
return '[' + node.type + ']'
100+
101+
def leave_NonNullType(self, node, *args):
102+
return node.type + '!'
103+
104+
105+
def join(maybe_list, separator=''):
106+
if maybe_list:
107+
return separator.join(filter(None, maybe_list))
108+
return ''
109+
110+
111+
def block(maybe_list):
112+
if maybe_list:
113+
return indent('{\n' + join(maybe_list, '\n')) + '\n}'
114+
return ''
115+
116+
117+
def wrap(start, maybe_str, end=''):
118+
if maybe_str:
119+
return start + maybe_str + end
120+
return ''
121+
122+
123+
def indent(maybe_str):
124+
if maybe_str:
125+
return maybe_str.replace('\n', '\n ')
126+
return maybe_str

tests/core_language/test_printer.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import copy
2+
from graphql.core.language.ast import Field, Name
3+
from graphql.core.language.parser import parse
4+
from graphql.core.language.printer import print_ast
5+
from pytest import raises
6+
from fixtures import KITCHEN_SINK
7+
8+
9+
def test_does_not_alter_ast():
10+
ast = parse(KITCHEN_SINK)
11+
ast_copy = copy.deepcopy(ast)
12+
print_ast(ast)
13+
assert ast == ast_copy
14+
15+
16+
def test_prints_minimal_ast():
17+
ast = Field(name=Name(loc=None, value='foo'),
18+
loc=None,
19+
alias=None,
20+
arguments=None,
21+
directives=None,
22+
selection_set=None)
23+
assert print_ast(ast) == 'foo'
24+
25+
26+
def test_produces_helpful_error_messages():
27+
bad_ast = {'random': 'Data'}
28+
with raises(Exception) as excinfo:
29+
print_ast(bad_ast)
30+
assert 'Invalid AST Node' in str(excinfo.value)
31+
32+
33+
def test_prints_kitchen_sink():
34+
ast = parse(KITCHEN_SINK)
35+
printed = print_ast(ast)
36+
assert printed == '''query queryName($foo: ComplexType, $site: Site = MOBILE) {
37+
whoever123is: node(id: [123, 456]) {
38+
id
39+
... on User @defer {
40+
field2 {
41+
id
42+
alias: field1(first: 10, after: $foo) @include(if: $foo) {
43+
id
44+
...frag
45+
}
46+
}
47+
}
48+
}
49+
}
50+
51+
mutation likeStory {
52+
like(story: 123) @defer {
53+
story {
54+
id
55+
}
56+
}
57+
}
58+
59+
fragment frag on Friend {
60+
foo(size: $size, bar: $b, obj: {key: "value"})
61+
}
62+
63+
{
64+
unnamed(truthy: true, falsey: false)
65+
query
66+
}
67+
'''

0 commit comments

Comments
 (0)