Skip to content

Commit

Permalink
Add caching for resolve (#3549)
Browse files Browse the repository at this point in the history
* Use cached property for .type

* More granular cache

* Added a comment for later

* Remove old cache and fix a bug noted by San

Let's see what happens if I remove the cache

* Add tweet and release files

* Fix name

* Remove comment

`resolve` is called every time we get the field type, which, turns out,
is called a lot of times, even when creating the dataclass, maybe
there's some opportunity for improvements here, but we would need
to call `resolve` anyway, so it's probably not worth changing how this
works
  • Loading branch information
patrick91 authored Jun 26, 2024
1 parent 095463d commit ea6a5c4
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 9 deletions.
4 changes: 4 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Release type: patch

This release improves the performance when returning a lot of data, especially
when using generic inputs (where we got a 7x speedup in our benchmark!).
6 changes: 6 additions & 0 deletions TWEET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
🆕 Release $version is out! Thanks to $contributor for the PR 👏

This release improves the performance when returning a lot of data, especially
when using generic inputs (where we got a 7x speedup in our benchmark!).

Get it here 👉 $release_url
20 changes: 12 additions & 8 deletions strawberry/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@


class StrawberryAnnotation:
__slots__ = "raw_annotation", "namespace", "__eval_cache__"
__slots__ = "raw_annotation", "namespace", "__resolve_cache__"

def __init__(
self,
Expand All @@ -65,7 +65,7 @@ def __init__(
self.raw_annotation = annotation
self.namespace = namespace

self.__eval_cache__: Optional[Type[Any]] = None
self.__resolve_cache__: Optional[Union[StrawberryType, type]] = None

def __eq__(self, other: object) -> bool:
if not isinstance(other, StrawberryAnnotation):
Expand Down Expand Up @@ -101,19 +101,17 @@ def annotation(self) -> Union[object, str]:
def annotation(self, value: Union[object, str]) -> None:
self.raw_annotation = value

self.__resolve_cache__ = None

def evaluate(self) -> type:
"""Return evaluated annotation using `strawberry.util.typing.eval_type`."""
evaled_type = self.__eval_cache__
if evaled_type:
return evaled_type

annotation = self.raw_annotation

if isinstance(annotation, str):
annotation = ForwardRef(annotation)

evaled_type = eval_type(annotation, self.namespace, None)

self.__eval_cache__ = evaled_type
return evaled_type

def _get_type_with_args(
Expand All @@ -131,6 +129,12 @@ def _get_type_with_args(

def resolve(self) -> Union[StrawberryType, type]:
"""Return resolved (transformed) annotation."""
if self.__resolve_cache__ is None:
self.__resolve_cache__ = self._resolve()

return self.__resolve_cache__

def _resolve(self) -> Union[StrawberryType, type]:
evaled_type = cast(Any, self.evaluate())

if is_private(evaled_type):
Expand Down Expand Up @@ -172,7 +176,7 @@ def set_namespace_from_field(self, field: StrawberryField) -> None:
module = sys.modules[field.origin.__module__]
self.namespace = module.__dict__

self.__eval_cache__ = None # Invalidate cache to allow re-evaluation
self.__resolve_cache__ = None # Invalidate cache to allow re-evaluation

def create_concrete_type(self, evaled_type: type) -> type:
if has_object_definition(evaled_type):
Expand Down
1 change: 0 additions & 1 deletion strawberry/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ def type(self) -> Union[StrawberryType, type]:

@property
def is_graphql_generic(self) -> bool:
# TODO: double check this
from strawberry.schema.compat import is_graphql_generic

return is_graphql_generic(self.type)
Expand Down

0 comments on commit ea6a5c4

Please sign in to comment.