Skip to content

Commit

Permalink
fix: async with new filter API (assert queryset is wrong) (#504)
Browse files Browse the repository at this point in the history
* fix: assert queryset is wrong (causing errors in async environments and
fetches implicitly the whole queryset)

* filters: add tests for the bug (empty queryset and async call of apply)

* tests: fix typing issues and django versions < django 4.1

* fix: add the tests also for v2 (will trigger the bug)

* fix filter v2 tests

* optimize: empty resolvers need no initialized db
  • Loading branch information
devkral authored Mar 19, 2024
1 parent 40a489b commit 1086f6c
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 1 deletion.
2 changes: 1 addition & 1 deletion strawberry_django/fields/filter_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def __call__( # type: ignore
kwargs[info_parameter.name] = info

if info_parameter := self.reserved_parameters.get(QUERYSET_PARAMSPEC):
assert queryset
assert queryset is not None
kwargs[info_parameter.name] = queryset

if info_parameter := self.reserved_parameters.get(SEQUENCE_PARAMSPEC):
Expand Down
49 changes: 49 additions & 0 deletions tests/filters/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from enum import Enum
from typing import Generic, List, Optional, TypeVar, cast

import django
import pytest
import strawberry
from django.test import override_settings
Expand Down Expand Up @@ -256,6 +257,54 @@ def fruits(self, filters: FruitFilter) -> List[Fruit]:
]


def test_empty_resolver_filter():
@strawberry.type
class Query:
@strawberry.field
def fruits(self, filters: FruitFilter) -> List[Fruit]:
queryset = models.Fruit.objects.none()
return cast(List[Fruit], strawberry_django.filters.apply(filters, queryset))

query = utils.generate_query(Query)
result = query('{ fruits(filters: { name: { exact: "strawberry" } }) { id name } }')
assert isinstance(result, ExecutionResult)
assert not result.errors
assert result.data is not None
assert result.data["fruits"] == []


@pytest.mark.asyncio()
@pytest.mark.django_db(transaction=True)
async def test_async_resolver_filter(fruits):
@strawberry.type
class Query:
@strawberry.field
async def fruits(self, filters: FruitFilter) -> List[Fruit]:
queryset = models.Fruit.objects.all()
queryset = strawberry_django.filters.apply(filters, queryset)
if django.VERSION < (4, 1):
from asgiref.sync import sync_to_async

@sync_to_async
def helper():
return cast(List[Fruit], list(queryset))

return await helper()
# cast fixes funny typing issue between list and List
return cast(List[Fruit], [fruit async for fruit in queryset])

query = utils.generate_query(Query)
result = await query( # type: ignore
'{ fruits(filters: { name: { exact: "strawberry" } }) { id name } }'
)
assert isinstance(result, ExecutionResult)
assert not result.errors
assert result.data is not None
assert result.data["fruits"] == [
{"id": "1", "name": "strawberry"},
]


def test_resolver_filter_with_inheritance(vegetables):
@strawberry.type
class Query:
Expand Down
54 changes: 54 additions & 0 deletions tests/filters/test_filters_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from enum import Enum
from typing import Any, List, Optional, cast

import django
import pytest
import strawberry
from django.db.models import Case, Count, Q, QuerySet, Value, When
from strawberry import auto
from strawberry.exceptions import MissingArgumentsAnnotationsError
from strawberry.relay import GlobalID
from strawberry.type import WithStrawberryObjectDefinition, get_object_definition
from strawberry.types import ExecutionResult

import strawberry_django
from strawberry_django.exceptions import (
Expand Down Expand Up @@ -380,3 +382,55 @@ def test_filter_distinct(query, db, fruits):
""")
assert not result.errors
assert len(result.data["fruits"]) == 1


def test_empty_resolver_filter():
@strawberry.type
class Query:
@strawberry.field
def fruits(self, filters: FruitFilter) -> List[Fruit]:
queryset = models.Fruit.objects.none()
_info: Any = object()
return cast(
List[Fruit], strawberry_django.filters.apply(filters, queryset, _info)
)

query = utils.generate_query(Query)
result = query('{ fruits(filters: { name: { exact: "strawberry" } }) { name } }')
assert isinstance(result, ExecutionResult)
assert not result.errors
assert result.data is not None
assert result.data["fruits"] == []


@pytest.mark.asyncio()
@pytest.mark.django_db(transaction=True)
async def test_async_resolver_filter(fruits):
@strawberry.type
class Query:
@strawberry.field
async def fruits(self, filters: FruitFilter) -> List[Fruit]:
queryset = models.Fruit.objects.all()
_info: Any = object()
queryset = strawberry_django.filters.apply(filters, queryset, _info)
if django.VERSION < (4, 1):
from asgiref.sync import sync_to_async

@sync_to_async
def helper():
return cast(List[Fruit], list(queryset))

return await helper()
# cast fixes funny typing issue between list and List
return cast(List[Fruit], [fruit async for fruit in queryset])

query = utils.generate_query(Query)
result = await query( # type: ignore
'{ fruits(filters: { name: { exact: "strawberry" } }) { name } }'
)
assert isinstance(result, ExecutionResult)
assert not result.errors
assert result.data is not None
assert result.data["fruits"] == [
{"name": "strawberry"},
]

0 comments on commit 1086f6c

Please sign in to comment.