Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions strawberry/execution/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Execution utilities for Strawberry GraphQL."""

from .is_awaitable import optimized_is_awaitable

__all__ = ["optimized_is_awaitable"]
76 changes: 76 additions & 0 deletions strawberry/execution/is_awaitable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Optimized is_awaitable implementation for GraphQL execution.

This module provides a highly optimized is_awaitable function that adds a fast path
for common synchronous types, significantly improving performance when dealing with
large result sets containing primitive values.
"""

from __future__ import annotations

import inspect
from types import CoroutineType, GeneratorType
from typing import Any

__all__ = ["optimized_is_awaitable"]

CO_ITERABLE_COROUTINE = inspect.CO_ITERABLE_COROUTINE

# Common synchronous types that are never awaitable
# Using a frozenset for O(1) lookup
_NON_AWAITABLE_TYPES: frozenset[type] = frozenset(
{
type(None),
bool,
int,
float,
str,
bytes,
bytearray,
list,
tuple,
dict,
set,
frozenset,
}
)


def optimized_is_awaitable(value: Any) -> bool:
"""Return true if object can be passed to an ``await`` expression.

This is an optimized version of graphql-core's is_awaitable that adds a fast path
for common synchronous types. For large result sets containing mostly primitive
values (ints, strings, lists, etc.), this can provide significant performance
improvements.

Performance characteristics:
- Fast path for primitives: O(1) type lookup
- Falls back to standard checks for other types
- Avoids expensive isinstance and hasattr calls for common types

Args:
value: The value to check

Returns:
True if the value is awaitable, False otherwise
"""
# Fast path: check if the type is a known non-awaitable type
# This single check replaces 3 checks (isinstance, isinstance, hasattr)
# for the most common case
value_type = type(value)
if value_type in _NON_AWAITABLE_TYPES:
return False

# For other types, use the standard graphql-core logic
# This handles coroutines, generators, and custom awaitable objects
return (
# check for coroutine objects
isinstance(value, CoroutineType)
# check for old-style generator based coroutine objects
or (
isinstance(value, GeneratorType)
and bool(value.gi_code.co_flags & CO_ITERABLE_COROUTINE)
)
# check for other awaitables (e.g. futures)
or hasattr(value, "__await__")
)
Comment on lines +64 to +76
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: The difference between this and https://github.com/graphql-python/graphql-core/blob/main/src/graphql/pyutils/is_awaitable.py#L20-L20 is the fast path, right?

We can possibly just import it from graphql-core and return graphql_core_is_awaitable(value) here, so we don't need to redefine this "not so obvious" code

3 changes: 3 additions & 0 deletions strawberry/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from strawberry import relay
from strawberry.annotation import StrawberryAnnotation
from strawberry.exceptions import MissingQueryError
from strawberry.execution import optimized_is_awaitable
from strawberry.extensions import SchemaExtension
from strawberry.extensions.directives import (
DirectivesExtension,
Expand Down Expand Up @@ -616,6 +617,7 @@ async def execute(
operation_name=execution_context.operation_name,
context_value=execution_context.context,
execution_context_class=self.execution_context_class,
is_awaitable=optimized_is_awaitable,
**custom_context_kwargs,
)
)
Expand Down Expand Up @@ -751,6 +753,7 @@ def execute_sync(
operation_name=execution_context.operation_name,
context_value=execution_context.context,
execution_context_class=self.execution_context_class,
is_awaitable=optimized_is_awaitable,
**custom_context_kwargs,
)

Expand Down
Loading