Skip to content

Commit 1cf868a

Browse files
committed
Merge pull request #4 from jhgg/visitor-validation-optimizations-2
Visitor validation optimizations.
2 parents 138f594 + a8d332a commit 1cf868a

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
@@ -45,48 +45,92 @@ def visit_using_rules(schema, ast, rules):
4545
type_info = TypeInfo(schema)
4646
context = ValidationContext(schema, ast, type_info)
4747
errors = []
48-
for rule in rules:
49-
instance = rule(context)
50-
visit(ast, ValidationVisitor(instance, type_info, errors))
48+
rules = [rule(context) for rule in rules]
49+
visit(ast, ValidationVisitor(rules, context, type_info, errors))
5150
return errors
5251

5352

5453
class ValidationVisitor(Visitor):
55-
def __init__(self, instance, type_info, errors):
56-
self.instance = instance
54+
def __init__(self, rules, context, type_info, errors):
55+
self.context = context
56+
self.rules = rules
57+
self.total_rules = len(rules)
5758
self.type_info = type_info
5859
self.errors = errors
60+
self.ignore_children = {}
5961

6062
def enter(self, node, key, parent, path, ancestors):
6163
self.type_info.enter(node)
64+
to_ignore = None
65+
rules_wanting_to_visit_fragment = None
6266

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

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

71-
if result is None and getattr(self.instance, 'visit_spread_fragments', False) and isinstance(node, FragmentSpread):
72-
fragment = self.instance.context.get_fragment(node.name.value)
73103
if fragment:
74-
visit(fragment, self)
104+
sub_visitor = ValidationVisitor(rules_wanting_to_visit_fragment, self.context, self.type_info,
105+
self.errors)
106+
visit(fragment, sub_visitor)
75107

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

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

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

84-
if result and is_error(result):
85-
append(self.errors, result)
86-
result = False
130+
if result and is_error(result):
131+
append(self.errors, result)
87132

88133
self.type_info.leave(node)
89-
return result
90134

91135

92136
def is_error(value):

0 commit comments

Comments
 (0)