From 1f0aeadb69475c0c1724aaa31cb8dbf88f0ab53b Mon Sep 17 00:00:00 2001 From: Dixing Xu Date: Sun, 8 Nov 2026 04:41:27 +0000 Subject: [PATCH 1/2] perf(collect_fields): skip directive checks -- 15% faster wide queries Check selection.directives once in visit_selection_sets() and call should_include_node() only when a selection has directives. Find @skip and @include in one plain loop instead of two generator expressions. Collecting a 4,000-field selection set with @include takes 22% less time; the wide-query benchmark goes from 73.9 ms to 63.1 ms. Output, errors and forbidden-directive lists are unchanged. Co-Authored-By: Aiden --- src/graphql/execution/collect_fields.py | 51 +++++++------------------ 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/src/graphql/execution/collect_fields.py b/src/graphql/execution/collect_fields.py index afbdc537..174a33eb 100644 --- a/src/graphql/execution/collect_fields.py +++ b/src/graphql/execution/collect_fields.py @@ -225,19 +225,17 @@ def visit_selection_sets( on_selection_set(selection_set) for selection in selection_set.selections: + if selection.directives and not should_include_node( + context, selection, variable_values, fragment_variable_values + ): + continue if isinstance(selection, FieldNode): - if not should_include_node( - context, selection, variable_values, fragment_variable_values - ): - continue key = get_field_entry_key(selection) grouped_field_set[key].append( FieldDetails(selection, defer_usage, fragment_variable_values) ) elif isinstance(selection, InlineFragmentNode): - if not should_include_node( - context, selection, variable_values, fragment_variable_values - ) or not does_fragment_condition_match(schema, selection, runtime_type): + if not does_fragment_condition_match(schema, selection, runtime_type): continue new_defer_usage = get_defer_usage( @@ -268,12 +266,6 @@ def visit_selection_sets( ) elif isinstance(selection, FragmentSpreadNode): # pragma: no branch frag_name = selection.name.value - - if not should_include_node( - context, selection, variable_values, fragment_variable_values - ): - continue - fragment = fragments.get(frag_name) if fragment is None or not does_fragment_condition_match( schema, fragment.definition, runtime_type @@ -379,18 +371,15 @@ def should_include_node( Determines if a field should be included based on the @include and @skip directives, where @skip has higher precedence than @include. """ - skip_directive_node = ( - next( - ( - directive - for directive in node.directives - if directive.name.value == GraphQLSkipDirective.name - ), - None, - ) - if node.directives - else None - ) + skip_directive_node: DirectiveNode | None = None + include_directive_node: DirectiveNode | None = None + for directive in node.directives or (): + name = directive.name.value + if skip_directive_node is None and name == GraphQLSkipDirective.name: + skip_directive_node = directive + elif include_directive_node is None and name == GraphQLIncludeDirective.name: + include_directive_node = directive + if skip_directive_node and context.forbid_skip_and_include: context.forbidden_directive_instances.append(skip_directive_node) return False @@ -408,18 +397,6 @@ def should_include_node( if skip and skip["if"]: return False - include_directive_node = ( - next( - ( - directive - for directive in node.directives - if directive.name.value == GraphQLIncludeDirective.name - ), - None, - ) - if node.directives - else None - ) if include_directive_node and context.forbid_skip_and_include: context.forbidden_directive_instances.append(include_directive_node) return False From ddd3d49e201969a9e570d6b52d91fe75eb3195a6 Mon Sep 17 00:00:00 2001 From: Dixing Xu Date: Sun, 8 Nov 2026 04:58:37 +0000 Subject: [PATCH 2/2] fix(collect_fields): type should_include_node node as SelectionNode The shared directive check passes any selection to should_include_node, so widen the annotation to fix the mypy lint error. Co-Authored-By: Aiden --- src/graphql/execution/collect_fields.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/graphql/execution/collect_fields.py b/src/graphql/execution/collect_fields.py index 174a33eb..a0e64035 100644 --- a/src/graphql/execution/collect_fields.py +++ b/src/graphql/execution/collect_fields.py @@ -12,6 +12,7 @@ FragmentSpreadNode, InlineFragmentNode, OperationDefinitionNode, + SelectionNode, SelectionSetNode, ) from ..pyutils import RefSet @@ -362,7 +363,7 @@ def get_defer_usage( def should_include_node( context: CollectFieldsContext, - node: FragmentSpreadNode | FieldNode | InlineFragmentNode, + node: SelectionNode, variable_values: VariableValues, fragment_variable_values: FragmentVariableValues | None = None, ) -> bool: