Skip to content

Commit 310d0a9

Browse files
committed
Use VisitorMeta to power TypeInfo so that it's not a huge isinstance chain.
1 parent fa504e7 commit 310d0a9

File tree

3 files changed

+179
-146
lines changed

3 files changed

+179
-146
lines changed

graphql/core/language/visitor.py

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,9 @@
11
from copy import copy
2+
23
import six
34
from . import ast
5+
from .visitor_meta import VisitorMeta, QUERY_DOCUMENT_KEYS
46

5-
QUERY_DOCUMENT_KEYS = {
6-
ast.Name: (),
7-
8-
ast.Document: ('definitions',),
9-
ast.OperationDefinition: ('name', 'variable_definitions', 'directives', 'selection_set'),
10-
ast.VariableDefinition: ('variable', 'type', 'default_value'),
11-
ast.Variable: ('name',),
12-
ast.SelectionSet: ('selections',),
13-
ast.Field: ('alias', 'name', 'arguments', 'directives', 'selection_set'),
14-
ast.Argument: ('name', 'value'),
15-
16-
ast.FragmentSpread: ('name', 'directives'),
17-
ast.InlineFragment: ('type_condition', 'directives', 'selection_set'),
18-
ast.FragmentDefinition: ('name', 'type_condition', 'directives', 'selection_set'),
19-
20-
ast.IntValue: (),
21-
ast.FloatValue: (),
22-
ast.StringValue: (),
23-
ast.BooleanValue: (),
24-
ast.EnumValue: (),
25-
ast.ListValue: ('values',),
26-
ast.ObjectValue: ('fields',),
27-
ast.ObjectField: ('name', 'value'),
28-
29-
ast.Directive: ('name', 'arguments'),
30-
31-
ast.NamedType: ('name',),
32-
ast.ListType: ('type',),
33-
ast.NonNullType: ('type',),
34-
35-
ast.ObjectTypeDefinition: ('name', 'interfaces', 'fields'),
36-
ast.FieldDefinition: ('name', 'arguments', 'type'),
37-
ast.InputValueDefinition: ('name', 'type', 'defaultValue'),
38-
ast.InterfaceTypeDefinition: ('name', 'fields'),
39-
ast.UnionTypeDefinition: ('name', 'types'),
40-
ast.ScalarTypeDefinition: ('name',),
41-
ast.EnumTypeDefinition: ('name', 'values'),
42-
ast.EnumValueDefinition: ('name',),
43-
ast.InputObjectTypeDefinition: ('name', 'fields'),
44-
ast.TypeExtensionDefinition: ('definition',),
45-
}
46-
47-
AST_KIND_TO_TYPE = {c.__name__: c for c in QUERY_DOCUMENT_KEYS.keys()}
487

498
BREAK = object()
509
REMOVE = object()
@@ -189,33 +148,6 @@ def visit(root, visitor, key_map=None):
189148
return new_root
190149

191150

192-
class VisitorMeta(type):
193-
def __new__(cls, name, bases, attrs):
194-
enter_handlers = {}
195-
leave_handlers = {}
196-
197-
for base in bases:
198-
if hasattr(base, '_enter_handlers'):
199-
enter_handlers.update(base._enter_handlers)
200-
if hasattr(base, '_leave_handlers'):
201-
leave_handlers.update(base._leave_handlers)
202-
203-
for attr, val in attrs.items():
204-
if attr.startswith('enter_'):
205-
ast_kind = attr[6:]
206-
ast_type = AST_KIND_TO_TYPE.get(ast_kind)
207-
enter_handlers[ast_type] = val
208-
209-
elif attr.startswith('leave_'):
210-
ast_kind = attr[6:]
211-
ast_type = AST_KIND_TO_TYPE.get(ast_kind)
212-
leave_handlers[ast_type] = val
213-
214-
attrs['_get_enter_handler'] = enter_handlers.get
215-
attrs['_get_leave_handler'] = leave_handlers.get
216-
return super(VisitorMeta, cls).__new__(cls, name, bases, attrs)
217-
218-
219151
@six.add_metaclass(VisitorMeta)
220152
class Visitor(object):
221153
def enter(self, node, key, parent, path, ancestors):
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from . import ast
2+
3+
QUERY_DOCUMENT_KEYS = {
4+
ast.Name: (),
5+
6+
ast.Document: ('definitions',),
7+
ast.OperationDefinition: ('name', 'variable_definitions', 'directives', 'selection_set'),
8+
ast.VariableDefinition: ('variable', 'type', 'default_value'),
9+
ast.Variable: ('name',),
10+
ast.SelectionSet: ('selections',),
11+
ast.Field: ('alias', 'name', 'arguments', 'directives', 'selection_set'),
12+
ast.Argument: ('name', 'value'),
13+
14+
ast.FragmentSpread: ('name', 'directives'),
15+
ast.InlineFragment: ('type_condition', 'directives', 'selection_set'),
16+
ast.FragmentDefinition: ('name', 'type_condition', 'directives', 'selection_set'),
17+
18+
ast.IntValue: (),
19+
ast.FloatValue: (),
20+
ast.StringValue: (),
21+
ast.BooleanValue: (),
22+
ast.EnumValue: (),
23+
ast.ListValue: ('values',),
24+
ast.ObjectValue: ('fields',),
25+
ast.ObjectField: ('name', 'value'),
26+
27+
ast.Directive: ('name', 'arguments'),
28+
29+
ast.NamedType: ('name',),
30+
ast.ListType: ('type',),
31+
ast.NonNullType: ('type',),
32+
33+
ast.ObjectTypeDefinition: ('name', 'interfaces', 'fields'),
34+
ast.FieldDefinition: ('name', 'arguments', 'type'),
35+
ast.InputValueDefinition: ('name', 'type', 'defaultValue'),
36+
ast.InterfaceTypeDefinition: ('name', 'fields'),
37+
ast.UnionTypeDefinition: ('name', 'types'),
38+
ast.ScalarTypeDefinition: ('name',),
39+
ast.EnumTypeDefinition: ('name', 'values'),
40+
ast.EnumValueDefinition: ('name',),
41+
ast.InputObjectTypeDefinition: ('name', 'fields'),
42+
ast.TypeExtensionDefinition: ('definition',),
43+
}
44+
45+
AST_KIND_TO_TYPE = {c.__name__: c for c in QUERY_DOCUMENT_KEYS.keys()}
46+
47+
48+
class VisitorMeta(type):
49+
def __new__(cls, name, bases, attrs):
50+
enter_handlers = {}
51+
leave_handlers = {}
52+
53+
for base in bases:
54+
if hasattr(base, '_enter_handlers'):
55+
enter_handlers.update(base._enter_handlers)
56+
if hasattr(base, '_leave_handlers'):
57+
leave_handlers.update(base._leave_handlers)
58+
59+
for attr, val in attrs.items():
60+
if attr.startswith('enter_'):
61+
ast_kind = attr[6:]
62+
ast_type = AST_KIND_TO_TYPE.get(ast_kind)
63+
enter_handlers[ast_type] = val
64+
65+
elif attr.startswith('leave_'):
66+
ast_kind = attr[6:]
67+
ast_type = AST_KIND_TO_TYPE.get(ast_kind)
68+
leave_handlers[ast_type] = val
69+
70+
attrs['_get_enter_handler'] = enter_handlers.get
71+
attrs['_get_leave_handler'] = leave_handlers.get
72+
return super(VisitorMeta, cls).__new__(cls, name, bases, attrs)

graphql/core/utils/type_info.py

Lines changed: 105 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from ..language import ast
1+
import six
2+
3+
from ..language import ast, visitor_meta
24
from ..type.definition import (
35
GraphQLInputObjectType,
46
GraphQLList,
@@ -16,6 +18,8 @@ def pop(lst):
1618
lst.pop()
1719

1820

21+
# noinspection PyPep8Naming
22+
@six.add_metaclass(visitor_meta.VisitorMeta)
1923
class TypeInfo(object):
2024
def __init__(self, schema):
2125
self._schema = schema
@@ -48,80 +52,105 @@ def get_directive(self):
4852
def get_argument(self):
4953
return self._argument
5054

55+
def leave(self, node):
56+
method = self._get_leave_handler(type(node))
57+
if method:
58+
return method(self)
59+
5160
def enter(self, node):
52-
schema = self._schema
53-
type = None
54-
if isinstance(node, ast.SelectionSet):
55-
named_type = get_named_type(self.get_type())
56-
composite_type = None
57-
if is_composite_type(named_type):
58-
composite_type = named_type
59-
self._parent_type_stack.append(composite_type)
60-
elif isinstance(node, ast.Field):
61-
parent_type = self.get_parent_type()
62-
field_def = None
63-
if parent_type:
64-
field_def = get_field_def(schema, parent_type, node)
65-
self._field_def_stack.append(field_def)
66-
self._type_stack.append(field_def and field_def.type)
67-
elif isinstance(node, ast.Directive):
68-
self._directive = schema.get_directive(node.name.value)
69-
elif isinstance(node, ast.OperationDefinition):
70-
if node.operation == 'query':
71-
type = schema.get_query_type()
72-
elif node.operation == 'mutation':
73-
type = schema.get_mutation_type()
74-
self._type_stack.append(type)
75-
elif isinstance(node, (ast.InlineFragment, ast.FragmentDefinition)):
76-
type_condition_ast = node.type_condition
77-
type = type_from_ast(schema, type_condition_ast) if type_condition_ast else self.get_type()
78-
self._type_stack.append(type)
79-
elif isinstance(node, ast.VariableDefinition):
80-
self._input_type_stack.append(type_from_ast(schema, node.type))
81-
elif isinstance(node, ast.Argument):
82-
arg_def = None
83-
arg_type = None
84-
field_or_directive = self.get_directive() or self.get_field_def()
85-
if field_or_directive:
86-
arg_def = [arg for arg in field_or_directive.args if arg.name == node.name.value]
87-
if arg_def:
88-
arg_def = arg_def[0]
89-
arg_type = arg_def.type
90-
else:
91-
arg_def = None
92-
self._argument = arg_def
93-
self._input_type_stack.append(arg_type)
94-
elif isinstance(node, ast.ListValue):
95-
list_type = get_nullable_type(self.get_input_type())
96-
self._input_type_stack.append(
97-
list_type.of_type if isinstance(list_type, GraphQLList) else None
98-
)
99-
elif isinstance(node, ast.ObjectField):
100-
object_type = get_named_type(self.get_input_type())
101-
field_type = None
102-
if isinstance(object_type, GraphQLInputObjectType):
103-
input_field = object_type.get_fields().get(node.name.value)
104-
field_type = input_field.type if input_field else None
105-
self._input_type_stack.append(field_type)
61+
method = self._get_enter_handler(type(node))
62+
if method:
63+
return method(self, node)
10664

107-
def leave(self, node):
108-
if isinstance(node, ast.SelectionSet):
109-
pop(self._parent_type_stack)
110-
elif isinstance(node, ast.Field):
111-
pop(self._field_def_stack)
112-
pop(self._type_stack)
113-
elif isinstance(node, ast.Directive):
114-
self._directive = None
115-
elif isinstance(node, (
116-
ast.OperationDefinition,
117-
ast.InlineFragment,
118-
ast.FragmentDefinition,
119-
)):
120-
pop(self._type_stack)
121-
elif isinstance(node, ast.VariableDefinition):
122-
pop(self._input_type_stack)
123-
elif isinstance(node, ast.Argument):
124-
self._argument = None
125-
pop(self._input_type_stack)
126-
elif isinstance(node, (ast.ListType, ast.ObjectField)):
127-
pop(self._input_type_stack)
65+
def enter_SelectionSet(self, node):
66+
named_type = get_named_type(self.get_type())
67+
composite_type = None
68+
if is_composite_type(named_type):
69+
composite_type = named_type
70+
self._parent_type_stack.append(composite_type)
71+
72+
def enter_Field(self, node):
73+
parent_type = self.get_parent_type()
74+
field_def = None
75+
if parent_type:
76+
field_def = get_field_def(self._schema, parent_type, node)
77+
self._field_def_stack.append(field_def)
78+
self._type_stack.append(field_def and field_def.type)
79+
80+
def enter_Directive(self, node):
81+
self._directive = self._schema.get_directive(node.name.value)
82+
83+
def enter_OperationDefinition(self, node):
84+
definition_type = None
85+
if node.operation == 'query':
86+
definition_type = self._schema.get_query_type()
87+
elif node.operation == 'mutation':
88+
definition_type = self._schema.get_mutation_type()
89+
90+
self._type_stack.append(definition_type)
91+
92+
def enter_InlineFragment(self, node):
93+
type_condition_ast = node.type_condition
94+
type = type_from_ast(self._schema, type_condition_ast) if type_condition_ast else self.get_type()
95+
self._type_stack.append(type)
96+
97+
enter_FragmentDefinition = enter_InlineFragment
98+
99+
def enter_VariableDefinition(self, node):
100+
self._input_type_stack.append(type_from_ast(self._schema, node.type))
101+
102+
def enter_Argument(self, node):
103+
arg_def = None
104+
arg_type = None
105+
field_or_directive = self.get_directive() or self.get_field_def()
106+
if field_or_directive:
107+
arg_def = [arg for arg in field_or_directive.args if arg.name == node.name.value]
108+
if arg_def:
109+
arg_def = arg_def[0]
110+
arg_type = arg_def.type
111+
else:
112+
arg_def = None
113+
self._argument = arg_def
114+
self._input_type_stack.append(arg_type)
115+
116+
def enter_ListValue(self, node):
117+
list_type = get_nullable_type(self.get_input_type())
118+
self._input_type_stack.append(
119+
list_type.of_type if isinstance(list_type, GraphQLList) else None
120+
)
121+
122+
def enter_ObjectField(self, node):
123+
object_type = get_named_type(self.get_input_type())
124+
field_type = None
125+
if isinstance(object_type, GraphQLInputObjectType):
126+
input_field = object_type.get_fields().get(node.name.value)
127+
field_type = input_field.type if input_field else None
128+
self._input_type_stack.append(field_type)
129+
130+
def leave_SelectionSet(self):
131+
pop(self._parent_type_stack)
132+
133+
def leave_Field(self):
134+
pop(self._field_def_stack)
135+
pop(self._type_stack)
136+
137+
def leave_Directive(self):
138+
self._directive = None
139+
140+
def leave_OperationDefinition(self):
141+
pop(self._type_stack)
142+
143+
leave_InlineFragment = leave_OperationDefinition
144+
leave_FragmentDefinition = leave_OperationDefinition
145+
146+
def leave_VariableDefinition(self):
147+
pop(self._input_type_stack)
148+
149+
def leave_Argument(self):
150+
self._argument = None
151+
pop(self._input_type_stack)
152+
153+
def leave_ListType(self):
154+
pop(self._input_type_stack)
155+
156+
leave_ObjectField = leave_ListType

0 commit comments

Comments
 (0)