Skip to content

Commit db5401a

Browse files
authored
Add custom is_awaitable to test perf (#4035)
* Add custom is_awaitable to test perf * Simplify is_awaitable by reusing graphql-core implementation Instead of duplicating the awaitable detection logic from graphql-core, import and use their is_awaitable function as a fallback. This keeps the fast path for common non-awaitable types while reducing code duplication and ensuring we stay in sync with graphql-core. * Add release notes for performance improvement
1 parent f2c275b commit db5401a

File tree

5 files changed

+5267
-0
lines changed

5 files changed

+5267
-0
lines changed

RELEASE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Release type: patch
2+
3+
Improved execution performance by up to 10% by adding an optimized `is_awaitable` check with a fast path for common synchronous types (such as int, str, list, dict, etc.).
4+
5+
This optimization reduces overhead when processing large result sets containing mostly basic values by avoiding expensive awaitable checks for types that are known to never be awaitable.

strawberry/execution/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Execution utilities for Strawberry GraphQL."""
2+
3+
from .is_awaitable import optimized_is_awaitable
4+
5+
__all__ = ["optimized_is_awaitable"]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Optimized is_awaitable implementation for GraphQL execution.
2+
3+
This module provides a highly optimized is_awaitable function that adds a fast path
4+
for common synchronous types, significantly improving performance when dealing with
5+
large result sets containing primitive values.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from typing import Any
11+
12+
from graphql.pyutils.is_awaitable import is_awaitable as graphql_core_is_awaitable
13+
14+
__all__ = ["optimized_is_awaitable"]
15+
16+
# Common synchronous types that are never awaitable
17+
# Using a frozenset for O(1) lookup
18+
_NON_AWAITABLE_TYPES: frozenset[type] = frozenset(
19+
{
20+
type(None),
21+
bool,
22+
int,
23+
float,
24+
str,
25+
bytes,
26+
bytearray,
27+
list,
28+
tuple,
29+
dict,
30+
set,
31+
frozenset,
32+
}
33+
)
34+
35+
36+
def optimized_is_awaitable(value: Any) -> bool:
37+
"""Return true if object can be passed to an ``await`` expression.
38+
39+
This is an optimized version of graphql-core's is_awaitable that adds a fast path
40+
for common synchronous types. For large result sets containing mostly primitive
41+
values (ints, strings, lists, etc.), this can provide significant performance
42+
improvements.
43+
44+
Performance characteristics:
45+
- Fast path for primitives: O(1) type lookup
46+
- Falls back to graphql-core's is_awaitable for other types
47+
48+
Args:
49+
value: The value to check
50+
51+
Returns:
52+
True if the value is awaitable, False otherwise
53+
"""
54+
# Fast path: check if the type is a known non-awaitable type
55+
if type(value) in _NON_AWAITABLE_TYPES:
56+
return False
57+
58+
# Fallback to graphql-core's implementation for other types
59+
return graphql_core_is_awaitable(value)

strawberry/schema/schema.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from strawberry import relay
4040
from strawberry.annotation import StrawberryAnnotation
4141
from strawberry.exceptions import MissingQueryError
42+
from strawberry.execution import optimized_is_awaitable
4243
from strawberry.extensions import SchemaExtension
4344
from strawberry.extensions.directives import (
4445
DirectivesExtension,
@@ -617,6 +618,7 @@ async def execute(
617618
operation_name=execution_context.operation_name,
618619
context_value=execution_context.context,
619620
execution_context_class=self.execution_context_class,
621+
is_awaitable=optimized_is_awaitable,
620622
**custom_context_kwargs,
621623
)
622624
)
@@ -752,6 +754,7 @@ def execute_sync(
752754
operation_name=execution_context.operation_name,
753755
context_value=execution_context.context,
754756
execution_context_class=self.execution_context_class,
757+
is_awaitable=optimized_is_awaitable,
755758
**custom_context_kwargs,
756759
)
757760

0 commit comments

Comments
 (0)