Skip to content

Commit f1febcf

Browse files
committed
Refactored visitor + Fix issue skipping subtrees in parallel visitor
Related GraphQL-js commit: graphql/graphql-js@699dc10
1 parent cab838c commit f1febcf

File tree

4 files changed

+153
-53
lines changed

4 files changed

+153
-53
lines changed

graphql/core/language/visitor.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,44 @@ def leave(self, node, key, parent, path, ancestors):
162162
method = self._get_leave_handler(type(node))
163163
if method:
164164
return method(self, node, key, parent, path, ancestors)
165+
166+
167+
class ParallelVisitor(Visitor):
168+
__slots__ = 'skipping', 'visitors'
169+
170+
def __init__(self, visitors):
171+
self.visitors = visitors
172+
self.skipping = [None] * len(visitors)
173+
174+
def enter(self, node, key, parent, path, ancestors):
175+
for i, visitor in enumerate(self.visitors):
176+
if not self.skipping[i]:
177+
result = visitor.enter(node, key, parent, path, ancestors)
178+
if result is False:
179+
self.skipping[i] = node
180+
181+
def leave(self, node, key, parent, path, ancestors):
182+
for i, visitor in enumerate(self.visitors):
183+
if not self.skipping[i]:
184+
visitor.leave(node, key, parent, path, ancestors)
185+
elif self.skipping[i] == node:
186+
self.skipping[i] = None
187+
188+
189+
class TypeInfoVisitor(Visitor):
190+
__slots__ = 'visitor', 'type_info'
191+
192+
def __init__(self, type_info, visitor):
193+
self.type_info = type_info
194+
self.visitor = visitor
195+
196+
def enter(self, node, key, parent, path, ancestors):
197+
self.type_info.enter(node)
198+
result = self.visitor.enter(node, key, parent, path, ancestors)
199+
if result is False:
200+
self.type_info.leave(node)
201+
return False
202+
203+
def leave(self, node, key, parent, path, ancestors):
204+
self.visitor.leave(node, key, parent, path, ancestors)
205+
self.type_info.leave(node)

graphql/core/validation/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
from ..language.visitor import visit
1+
from ..language.visitor import visit, ParallelVisitor, TypeInfoVisitor
22
from ..type import GraphQLSchema
33
from ..utils.type_info import TypeInfo
44
from .context import ValidationContext
55
from .rules import specified_rules
6-
from .visitor import ParallelVisitor, TypeInfoVisitor
76

87

98
def validate(schema, ast, rules=specified_rules):

graphql/core/validation/visitor.py

Lines changed: 0 additions & 42 deletions
This file was deleted.

tests/core_language/test_visitor.py

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from graphql.core.language.ast import Field, Name, SelectionSet
22
from graphql.core.language.parser import parse
3-
from graphql.core.language.visitor import BREAK, REMOVE, Visitor, visit
3+
from graphql.core.language.visitor import BREAK, REMOVE, Visitor, visit, ParallelVisitor
44

55
from .fixtures import KITCHEN_SINK
66

@@ -50,7 +50,8 @@ def enter(self, node, *args):
5050
selections = []
5151
if selection_set:
5252
selections = selection_set.selections
53-
new_selection_set = SelectionSet(selections=[added_field] + selections)
53+
new_selection_set = SelectionSet(
54+
selections=[added_field] + selections)
5455
return Field(name=None, selection_set=new_selection_set)
5556
if node is added_field:
5657
self.did_visit_added_field = True
@@ -67,12 +68,14 @@ def test_allows_skipping_a_subtree():
6768
class TestVisitor(Visitor):
6869

6970
def enter(self, node, *args):
70-
visited.append(['enter', type(node).__name__, getattr(node, 'value', None)])
71+
visited.append(
72+
['enter', type(node).__name__, getattr(node, 'value', None)])
7173
if isinstance(node, Field) and node.name.value == 'b':
7274
return False
7375

7476
def leave(self, node, *args):
75-
visited.append(['leave', type(node).__name__, getattr(node, 'value', None)])
77+
visited.append(
78+
['leave', type(node).__name__, getattr(node, 'value', None)])
7679

7780
visit(ast, TestVisitor())
7881

@@ -102,12 +105,14 @@ def test_allows_early_exit_while_visiting():
102105
class TestVisitor(Visitor):
103106

104107
def enter(self, node, *args):
105-
visited.append(['enter', type(node).__name__, getattr(node, 'value', None)])
108+
visited.append(
109+
['enter', type(node).__name__, getattr(node, 'value', None)])
106110
if isinstance(node, Name) and node.value == 'x':
107111
return BREAK
108112

109113
def leave(self, node, *args):
110-
visited.append(['leave', type(node).__name__, getattr(node, 'value', None)])
114+
visited.append(
115+
['leave', type(node).__name__, getattr(node, 'value', None)])
111116

112117
visit(ast, TestVisitor())
113118

@@ -135,13 +140,16 @@ def test_allows_a_named_functions_visitor_api():
135140
class TestVisitor(Visitor):
136141

137142
def enter_Name(self, node, *args):
138-
visited.append(['enter', type(node).__name__, getattr(node, 'value', None)])
143+
visited.append(
144+
['enter', type(node).__name__, getattr(node, 'value', None)])
139145

140146
def enter_SelectionSet(self, node, *args):
141-
visited.append(['enter', type(node).__name__, getattr(node, 'value', None)])
147+
visited.append(
148+
['enter', type(node).__name__, getattr(node, 'value', None)])
142149

143150
def leave_SelectionSet(self, node, *args):
144-
visited.append(['leave', type(node).__name__, getattr(node, 'value', None)])
151+
visited.append(
152+
['leave', type(node).__name__, getattr(node, 'value', None)])
145153

146154
visit(ast, TestVisitor())
147155

@@ -476,3 +484,97 @@ def leave(self, node, key, parent, *args):
476484
['leave', 'OperationDefinition', 4, None],
477485
['leave', 'Document', None, None]
478486
]
487+
488+
489+
def test_visits_in_pararell_allows_skipping_a_subtree():
490+
visited = []
491+
ast = parse('{ a, b { x }, c }')
492+
493+
class TestVisitor(Visitor):
494+
495+
def enter(self, node, key, parent, *args):
496+
visited.append(
497+
['enter', type(node).__name__, getattr(node, 'value', None)])
498+
if type(node).__name__ == 'Field' and node.name.value == 'b':
499+
return False
500+
501+
def leave(self, node, key, parent, *args):
502+
visited.append(
503+
['leave', type(node).__name__, getattr(node, 'value', None)])
504+
505+
visit(ast, ParallelVisitor([TestVisitor()]))
506+
assert visited == [
507+
['enter', 'Document', None],
508+
['enter', 'OperationDefinition', None],
509+
['enter', 'SelectionSet', None],
510+
['enter', 'Field', None],
511+
['enter', 'Name', 'a'],
512+
['leave', 'Name', 'a'],
513+
['leave', 'Field', None],
514+
['enter', 'Field', None],
515+
['enter', 'Field', None],
516+
['enter', 'Name', 'c'],
517+
['leave', 'Name', 'c'],
518+
['leave', 'Field', None],
519+
['leave', 'SelectionSet', None],
520+
['leave', 'OperationDefinition', None],
521+
['leave', 'Document', None],
522+
]
523+
524+
525+
def test_visits_in_pararell_allows_skipping_different_subtrees():
526+
visited = []
527+
ast = parse('{ a { x }, b { y} }')
528+
529+
class TestVisitor(Visitor):
530+
531+
def __init__(self, name):
532+
self.name = name
533+
534+
def enter(self, node, key, parent, *args):
535+
visited.append(["no-{}".format(self.name), 'enter',
536+
type(node).__name__, getattr(node, 'value', None)])
537+
if type(node).__name__ == 'Field' and node.name.value == self.name:
538+
return False
539+
540+
def leave(self, node, key, parent, *args):
541+
visited.append(["no-{}".format(self.name), 'leave',
542+
type(node).__name__, getattr(node, 'value', None)])
543+
544+
visit(ast, ParallelVisitor([TestVisitor('a'), TestVisitor('b')]))
545+
assert visited == [
546+
['no-a', 'enter', 'Document', None],
547+
['no-b', 'enter', 'Document', None],
548+
['no-a', 'enter', 'OperationDefinition', None],
549+
['no-b', 'enter', 'OperationDefinition', None],
550+
['no-a', 'enter', 'SelectionSet', None],
551+
['no-b', 'enter', 'SelectionSet', None],
552+
['no-a', 'enter', 'Field', None],
553+
['no-b', 'enter', 'Field', None],
554+
['no-b', 'enter', 'Name', 'a'],
555+
['no-b', 'leave', 'Name', 'a'],
556+
['no-b', 'enter', 'SelectionSet', None],
557+
['no-b', 'enter', 'Field', None],
558+
['no-b', 'enter', 'Name', 'x'],
559+
['no-b', 'leave', 'Name', 'x'],
560+
['no-b', 'leave', 'Field', None],
561+
['no-b', 'leave', 'SelectionSet', None],
562+
['no-b', 'leave', 'Field', None],
563+
['no-a', 'enter', 'Field', None],
564+
['no-b', 'enter', 'Field', None],
565+
['no-a', 'enter', 'Name', 'b'],
566+
['no-a', 'leave', 'Name', 'b'],
567+
['no-a', 'enter', 'SelectionSet', None],
568+
['no-a', 'enter', 'Field', None],
569+
['no-a', 'enter', 'Name', 'y'],
570+
['no-a', 'leave', 'Name', 'y'],
571+
['no-a', 'leave', 'Field', None],
572+
['no-a', 'leave', 'SelectionSet', None],
573+
['no-a', 'leave', 'Field', None],
574+
['no-a', 'leave', 'SelectionSet', None],
575+
['no-b', 'leave', 'SelectionSet', None],
576+
['no-a', 'leave', 'OperationDefinition', None],
577+
['no-b', 'leave', 'OperationDefinition', None],
578+
['no-a', 'leave', 'Document', None],
579+
['no-b', 'leave', 'Document', None],
580+
]

0 commit comments

Comments
 (0)