From 31bb262b09e310d969c48f2027440d1637cc6f6c Mon Sep 17 00:00:00 2001 From: Thiago Bellini Ribeiro Date: Sat, 19 Oct 2024 13:40:51 -0300 Subject: [PATCH] fix: Make sure that async fields always return Awaitables (#646) --- strawberry_django/fields/field.py | 5 +- tests/test_field_permissions.py | 272 ++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 tests/test_field_permissions.py diff --git a/strawberry_django/fields/field.py b/strawberry_django/fields/field.py index 20a512fc..e1d921f5 100644 --- a/strawberry_django/fields/field.py +++ b/strawberry_django/fields/field.py @@ -28,6 +28,7 @@ from strawberry.annotation import StrawberryAnnotation from strawberry.types.fields.resolver import StrawberryResolver from strawberry.types.info import Info # noqa: TCH002 +from strawberry.utils.await_maybe import await_maybe from strawberry_django import optimizer from strawberry_django.arguments import argument @@ -215,10 +216,10 @@ def get_result( if isinstance(attr, FileDescriptor) and not result: result = None - if is_awaitable: + if is_awaitable or self.is_async: async def async_resolver(): - resolved = await result # type: ignore + resolved = await await_maybe(result) if isinstance(resolved, BaseManager): resolved = resolve_base_manager(resolved) diff --git a/tests/test_field_permissions.py b/tests/test_field_permissions.py new file mode 100644 index 00000000..6176efc9 --- /dev/null +++ b/tests/test_field_permissions.py @@ -0,0 +1,272 @@ +from collections.abc import Awaitable +from typing import Any, Union + +import pytest +import strawberry +from strawberry import BasePermission, Info + +import strawberry_django +from strawberry_django.optimizer import DjangoOptimizerExtension +from tests import models + + +@pytest.mark.django_db(transaction=True) +async def test_with_async_permission(db): + class AsyncPermission(BasePermission): + async def has_permission( # type: ignore + self, + source: Any, + info: Info, + **kwargs: Any, + ) -> Union[bool, Awaitable[bool]]: + return True + + @strawberry_django.type(models.Fruit) + class Fruit: + name: strawberry.auto + + @strawberry_django.type(models.Color) + class Color: + name: strawberry.auto + fruits: list[Fruit] = strawberry_django.field( + permission_classes=[AsyncPermission] + ) + + @strawberry.type(name="Query") + class Query: + colors: list[Color] = strawberry_django.field() + + red = await models.Color.objects.acreate(name="Red") + yellow = await models.Color.objects.acreate(name="Yellow") + + await models.Fruit.objects.acreate(name="Apple", color=red) + await models.Fruit.objects.acreate(name="Banana", color=yellow) + await models.Fruit.objects.acreate(name="Strawberry", color=red) + + schema = strawberry.Schema(query=Query) + query = """ + query { + colors { + name + fruits { + name + } + } + } + """ + + result = await schema.execute(query) + assert result.errors is None + assert result.data == { + "colors": [ + { + "name": "Red", + "fruits": [ + {"name": "Apple"}, + {"name": "Strawberry"}, + ], + }, + { + "name": "Yellow", + "fruits": [{"name": "Banana"}], + }, + ] + } + + +@pytest.mark.django_db(transaction=True) +async def test_with_async_permission_and_optimizer(db): + class AsyncPermission(BasePermission): + async def has_permission( # type: ignore + self, + source: Any, + info: Info, + **kwargs: Any, + ) -> Union[bool, Awaitable[bool]]: + return True + + @strawberry_django.type(models.Fruit) + class Fruit: + name: strawberry.auto + + @strawberry_django.type(models.Color) + class Color: + name: strawberry.auto + fruits: list[Fruit] = strawberry_django.field( + permission_classes=[AsyncPermission] + ) + + @strawberry.type(name="Query") + class Query: + colors: list[Color] = strawberry_django.field() + + red = await models.Color.objects.acreate(name="Red") + yellow = await models.Color.objects.acreate(name="Yellow") + + await models.Fruit.objects.acreate(name="Apple", color=red) + await models.Fruit.objects.acreate(name="Banana", color=yellow) + await models.Fruit.objects.acreate(name="Strawberry", color=red) + + schema = strawberry.Schema( + query=Query, + extensions=[DjangoOptimizerExtension()], + ) + query = """ + query { + colors { + name + fruits { + name + } + } + } + """ + + result = await schema.execute(query) + assert result.errors is None + assert result.data == { + "colors": [ + { + "name": "Red", + "fruits": [ + {"name": "Apple"}, + {"name": "Strawberry"}, + ], + }, + { + "name": "Yellow", + "fruits": [{"name": "Banana"}], + }, + ] + } + + +@pytest.mark.django_db(transaction=True) +def test_with_sync_permission(db): + class AsyncPermission(BasePermission): + def has_permission( + self, + source: Any, + info: Info, + **kwargs: Any, + ) -> Union[bool, Awaitable[bool]]: + return True + + @strawberry_django.type(models.Fruit) + class Fruit: + name: strawberry.auto + + @strawberry_django.type(models.Color) + class Color: + name: strawberry.auto + fruits: list[Fruit] = strawberry_django.field( + permission_classes=[AsyncPermission] + ) + + @strawberry.type(name="Query") + class Query: + colors: list[Color] = strawberry_django.field() + + red = models.Color.objects.create(name="Red") + yellow = models.Color.objects.create(name="Yellow") + + models.Fruit.objects.create(name="Apple", color=red) + models.Fruit.objects.create(name="Banana", color=yellow) + models.Fruit.objects.create(name="Strawberry", color=red) + + schema = strawberry.Schema(query=Query) + query = """ + query { + colors { + name + fruits { + name + } + } + } + """ + + result = schema.execute_sync(query) + assert result.errors is None + assert result.data == { + "colors": [ + { + "name": "Red", + "fruits": [ + {"name": "Apple"}, + {"name": "Strawberry"}, + ], + }, + { + "name": "Yellow", + "fruits": [{"name": "Banana"}], + }, + ] + } + + +@pytest.mark.django_db(transaction=True) +def test_with_sync_permission_and_optimizer(db): + class AsyncPermission(BasePermission): + def has_permission( + self, + source: Any, + info: Info, + **kwargs: Any, + ) -> Union[bool, Awaitable[bool]]: + return True + + @strawberry_django.type(models.Fruit) + class Fruit: + name: strawberry.auto + + @strawberry_django.type(models.Color) + class Color: + name: strawberry.auto + fruits: list[Fruit] = strawberry_django.field( + permission_classes=[AsyncPermission] + ) + + @strawberry.type(name="Query") + class Query: + colors: list[Color] = strawberry_django.field() + + red = models.Color.objects.create(name="Red") + yellow = models.Color.objects.create(name="Yellow") + + models.Fruit.objects.create(name="Apple", color=red) + models.Fruit.objects.create(name="Banana", color=yellow) + models.Fruit.objects.create(name="Strawberry", color=red) + + schema = strawberry.Schema( + query=Query, + extensions=[DjangoOptimizerExtension()], + ) + query = """ + query { + colors { + name + fruits { + name + } + } + } + """ + + result = schema.execute_sync(query) + assert result.errors is None + assert result.data == { + "colors": [ + { + "name": "Red", + "fruits": [ + {"name": "Apple"}, + {"name": "Strawberry"}, + ], + }, + { + "name": "Yellow", + "fruits": [{"name": "Banana"}], + }, + ] + }