Skip to content

Commit a8d332a

Browse files
committed
Visitor validation optimizations.
1 parent be05925 commit a8d332a

File tree

1 file changed

+65
-21
lines changed

1 file changed

+65
-21
lines changed

graphql/core/validation/__init__.py

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,48 +44,92 @@ def visit_using_rules(schema, ast, rules):
4444
type_info = TypeInfo(schema)
4545
context = ValidationContext(schema, ast, type_info)
4646
errors = []
47-
for rule in rules:
48-
instance = rule(context)
49-
visit(ast, ValidationVisitor(instance, type_info, errors))
47+
rules = [rule(context) for rule in rules]
48+
visit(ast, ValidationVisitor(rules, context, type_info, errors))
5049
return errors
5150

5251

5352
class ValidationVisitor(Visitor):
54-
def __init__(self, instance, type_info, errors):
55-
self.instance = instance
53+
def __init__(self, rules, context, type_info, errors):
54+
self.context = context
55+
self.rules = rules
56+
self.total_rules = len(rules)
5657
self.type_info = type_info
5758
self.errors = errors
59+
self.ignore_children = {}
5860

5961
def enter(self, node, key, parent, path, ancestors):
6062
self.type_info.enter(node)
63+
to_ignore = None
64+
rules_wanting_to_visit_fragment = None
6165

62-
if isinstance(node, FragmentDefinition) and key and hasattr(self.instance, 'visit_spread_fragments'):
63-
return False
66+
skipped = 0
67+
for rule in self.rules:
68+
if rule in self.ignore_children:
69+
skipped += 1
70+
continue
71+
72+
visit_spread_fragments = getattr(rule, 'visit_spread_fragments', False)
73+
74+
if isinstance(node, FragmentDefinition) and key and visit_spread_fragments:
75+
if to_ignore is None:
76+
to_ignore = []
77+
78+
to_ignore.append(rule)
79+
continue
80+
81+
result = rule.enter(node, key, parent, path, ancestors)
82+
83+
if result and is_error(result):
84+
append(self.errors, result)
85+
result = False
86+
87+
if result is None and visit_spread_fragments and isinstance(node, FragmentSpread):
88+
if rules_wanting_to_visit_fragment is None:
89+
rules_wanting_to_visit_fragment = []
90+
91+
rules_wanting_to_visit_fragment.append(rule)
6492

65-
result = self.instance.enter(node, key, parent, path, ancestors)
66-
if result and is_error(result):
67-
append(self.errors, result)
68-
result = False
93+
if result is False:
94+
if to_ignore is None:
95+
to_ignore = []
96+
97+
to_ignore.append(rule)
98+
99+
if rules_wanting_to_visit_fragment:
100+
fragment = self.context.get_fragment(node.name.value)
69101

70-
if result is None and getattr(self.instance, 'visit_spread_fragments', False) and isinstance(node, FragmentSpread):
71-
fragment = self.instance.context.get_fragment(node.name.value)
72102
if fragment:
73-
visit(fragment, self)
103+
sub_visitor = ValidationVisitor(rules_wanting_to_visit_fragment, self.context, self.type_info,
104+
self.errors)
105+
visit(fragment, sub_visitor)
74106

75-
if result is False:
107+
should_skip = (len(to_ignore) if to_ignore else 0 + skipped) == self.total_rules
108+
109+
if should_skip:
76110
self.type_info.leave(node)
77111

78-
return result
112+
elif to_ignore:
113+
for rule in to_ignore:
114+
self.ignore_children[rule] = node
115+
116+
if should_skip:
117+
return False
79118

80119
def leave(self, node, key, parent, path, ancestors):
81-
result = self.instance.leave(node, key, parent, path, ancestors)
120+
for rule in self.rules:
121+
if rule in self.ignore_children:
122+
if self.ignore_children[rule] is node:
123+
del self.ignore_children[rule]
124+
125+
continue
126+
127+
result = rule.leave(node, key, parent, path, ancestors)
82128

83-
if result and is_error(result):
84-
append(self.errors, result)
85-
result = False
129+
if result and is_error(result):
130+
append(self.errors, result)
86131

87132
self.type_info.leave(node)
88-
return result
89133

90134

91135
def is_error(value):

0 commit comments

Comments
 (0)