From 6a5b28d09f6ea151c0eebba07bfb328230ae9767 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 30 Mar 2023 19:32:36 +0100
Subject: [PATCH 01/35] Resolve DjangoObjectType getNode when in an async
 context

---
 examples/cookbook/cookbook/urls.py | 7 +++----
 graphene_django/types.py           | 8 ++++++++
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/examples/cookbook/cookbook/urls.py b/examples/cookbook/cookbook/urls.py
index 6f8a3021c..e9e69cd25 100644
--- a/examples/cookbook/cookbook/urls.py
+++ b/examples/cookbook/cookbook/urls.py
@@ -1,10 +1,9 @@
-from django.conf.urls import url
+from django.urls import re_path
 from django.contrib import admin
 
 from graphene_django.views import GraphQLView
 
-
 urlpatterns = [
-    url(r"^admin/", admin.site.urls),
-    url(r"^graphql$", GraphQLView.as_view(graphiql=True)),
+    re_path(r"^admin/", admin.site.urls),
+    re_path(r"^graphql$", GraphQLView.as_view(graphiql=True)),
 ]
diff --git a/graphene_django/types.py b/graphene_django/types.py
index a6e54af41..068d2680d 100644
--- a/graphene_django/types.py
+++ b/graphene_django/types.py
@@ -288,6 +288,14 @@ def get_queryset(cls, queryset, info):
     def get_node(cls, info, id):
         queryset = cls.get_queryset(cls._meta.model.objects, info)
         try:
+            try: 
+                import asyncio
+                asyncio.get_running_loop()
+            except RuntimeError:
+                pass
+            else:
+                return queryset.aget(pk=id)
+            
             return queryset.get(pk=id)
         except cls._meta.model.DoesNotExist:
             return None

From 74998afb69ad75d358c5853d3504d69e1b54618c Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 30 Mar 2023 19:49:27 +0100
Subject: [PATCH 02/35] Support Django Connection resolving in an async context

---
 graphene_django/fields.py | 38 +++++++++++++++++++++++++++++++-------
 1 file changed, 31 insertions(+), 7 deletions(-)

diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 0fe123deb..80107ea85 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -9,7 +9,8 @@
     offset_to_cursor,
 )
 
-from promise import Promise
+from asgiref.sync import sync_to_async
+from asyncio import get_running_loop
 
 from graphene import Int, NonNull
 from graphene.relay import ConnectionField
@@ -228,20 +229,43 @@ def connection_resolver(
 
         # eventually leads to DjangoObjectType's get_queryset (accepts queryset)
         # or a resolve_foo (does not accept queryset)
+
         iterable = resolver(root, info, **args)
+        if info.is_awaitable(iterable):
+            async def await_result():
+                queryset_or_list = await iterable
+                if queryset_or_list is None:
+                    queryset_or_list = default_manager
+
+                    if is_async(queryset_resolver):
+                    
+                        resolved = await sync_to_async(queryset_resolver)(connection, resolved, info, args)
+
+                    # TODO: create an async_resolve_connection which uses the new Django queryset async functions
+                    async_resolve_connection = sync_to_async(cls.resolve_connection)
+
+                    if is_awaitable(resolved):
+                        return async_resolve_connection(connection, args, await resolved, max_limit=max_limit)
+                    
+                    return async_resolve_connection(connection, args, resolved, max_limit=max_limit)
+            
+            return await_result()
+
         if iterable is None:
             iterable = default_manager
         # thus the iterable gets refiltered by resolve_queryset
         # but iterable might be promise
         iterable = queryset_resolver(connection, iterable, info, args)
-        on_resolve = partial(
-            cls.resolve_connection, connection, args, max_limit=max_limit
-        )
 
-        if Promise.is_thenable(iterable):
-            return Promise.resolve(iterable).then(on_resolve)
+        try: 
+            get_running_loop()
+        except RuntimeError:
+                pass
+        else:
+            return sync_to_async(cls.resolve_connection)(connection, args, iterable, max_limit=max_limit)
+
 
-        return on_resolve(iterable)
+        return cls.resolve_connection(connection, args, iterable, max_limit=max_limit)
 
     def wrap_resolve(self, parent_resolver):
         return partial(

From f04f0d33ff288a4345a57cf490074fcea3f7465b Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 30 Mar 2023 19:51:40 +0100
Subject: [PATCH 03/35] Support foriegn key connections running async

---
 graphene_django/converter.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/graphene_django/converter.py b/graphene_django/converter.py
index 375d68312..dbd7cb8db 100644
--- a/graphene_django/converter.py
+++ b/graphene_django/converter.py
@@ -1,5 +1,6 @@
 from collections import OrderedDict
 from functools import singledispatch, wraps
+from asyncio import get_running_loop
 
 from django.db import models
 from django.utils.encoding import force_str

From e78fb86ce66941cc5e0f71046baddd97ae196897 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 30 Mar 2023 20:27:15 +0100
Subject: [PATCH 04/35] handle regualr django lists

---
 graphene_django/fields.py | 28 ++++++++--------------------
 1 file changed, 8 insertions(+), 20 deletions(-)

diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 80107ea85..68db6a017 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -61,6 +61,13 @@ def list_resolver(
             # Pass queryset to the DjangoObjectType get_queryset method
             queryset = maybe_queryset(django_object_type.get_queryset(queryset, info))
 
+        try: 
+            get_running_loop()
+        except RuntimeError:
+                pass
+        else:
+            return queryset.aiterator()
+
         return queryset
 
     def wrap_resolve(self, parent_resolver):
@@ -231,26 +238,7 @@ def connection_resolver(
         # or a resolve_foo (does not accept queryset)
 
         iterable = resolver(root, info, **args)
-        if info.is_awaitable(iterable):
-            async def await_result():
-                queryset_or_list = await iterable
-                if queryset_or_list is None:
-                    queryset_or_list = default_manager
-
-                    if is_async(queryset_resolver):
-                    
-                        resolved = await sync_to_async(queryset_resolver)(connection, resolved, info, args)
-
-                    # TODO: create an async_resolve_connection which uses the new Django queryset async functions
-                    async_resolve_connection = sync_to_async(cls.resolve_connection)
-
-                    if is_awaitable(resolved):
-                        return async_resolve_connection(connection, args, await resolved, max_limit=max_limit)
-                    
-                    return async_resolve_connection(connection, args, resolved, max_limit=max_limit)
-            
-            return await_result()
-
+        
         if iterable is None:
             iterable = default_manager
         # thus the iterable gets refiltered by resolve_queryset

From 28846f9ac954c91422d51f85798ecc703a6eb5b9 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 30 Mar 2023 21:01:42 +0100
Subject: [PATCH 05/35] drop in an async view

---
 graphene_django/views.py | 354 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 354 insertions(+)

diff --git a/graphene_django/views.py b/graphene_django/views.py
index b29aeede7..1ea6db8ae 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -2,12 +2,15 @@
 import json
 import re
 
+from asyncio import gather, coroutines
+
 from django.db import connection, transaction
 from django.http import HttpResponse, HttpResponseNotAllowed
 from django.http.response import HttpResponseBadRequest
 from django.shortcuts import render
 from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import ensure_csrf_cookie
+from django.utils.decorators import classonlymethod
 from django.views.generic import View
 from graphql import OperationType, get_operation_ast, parse, validate
 from graphql.error import GraphQLError
@@ -396,3 +399,354 @@ def get_content_type(request):
         meta = request.META
         content_type = meta.get("CONTENT_TYPE", meta.get("HTTP_CONTENT_TYPE", ""))
         return content_type.split(";", 1)[0].lower()
+
+
+class AsyncGraphQLView(GraphQLView):
+    graphiql_template = "graphene/graphiql.html"
+
+    # Polyfill for window.fetch.
+    whatwg_fetch_version = "3.6.2"
+    whatwg_fetch_sri = "sha256-+pQdxwAcHJdQ3e/9S4RK6g8ZkwdMgFQuHvLuN5uyk5c="
+
+    # React and ReactDOM.
+    react_version = "17.0.2"
+    react_sri = "sha256-Ipu/TQ50iCCVZBUsZyNJfxrDk0E2yhaEIz0vqI+kFG8="
+    react_dom_sri = "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0="
+
+    # The GraphiQL React app.
+    graphiql_version = "1.4.7"  # "1.0.3"
+    graphiql_sri = "sha256-cpZ8w9D/i6XdEbY/Eu7yAXeYzReVw0mxYd7OU3gUcsc="  # "sha256-VR4buIDY9ZXSyCNFHFNik6uSe0MhigCzgN4u7moCOTk="
+    graphiql_css_sri = "sha256-HADQowUuFum02+Ckkv5Yu5ygRoLllHZqg0TFZXY7NHI="  # "sha256-LwqxjyZgqXDYbpxQJ5zLQeNcf7WVNSJ+r8yp2rnWE/E="
+
+    # The websocket transport library for subscriptions.
+    subscriptions_transport_ws_version = "0.9.18"
+    subscriptions_transport_ws_sri = (
+        "sha256-i0hAXd4PdJ/cHX3/8tIy/Q/qKiWr5WSTxMFuL9tACkw="
+    )
+
+    schema = None
+    graphiql = False
+    middleware = None
+    root_value = None
+    pretty = False
+    batch = False
+    subscription_path = None
+    execution_context_class = None
+
+    def __init__(
+        self,
+        schema=None,
+        middleware=None,
+        root_value=None,
+        graphiql=False,
+        pretty=False,
+        batch=False,
+        subscription_path=None,
+        execution_context_class=None,
+    ):
+        if not schema:
+            schema = graphene_settings.SCHEMA
+
+        if middleware is None:
+            middleware = graphene_settings.MIDDLEWARE
+
+        self.schema = self.schema or schema
+        if middleware is not None:
+            if isinstance(middleware, MiddlewareManager):
+                self.middleware = middleware
+            else:
+                self.middleware = list(instantiate_middleware(middleware))
+        self.root_value = root_value
+        self.pretty = self.pretty or pretty
+        self.graphiql = self.graphiql or graphiql
+        self.batch = self.batch or batch
+        self.execution_context_class = execution_context_class
+        if subscription_path is None:
+            self.subscription_path = graphene_settings.SUBSCRIPTION_PATH
+
+        assert isinstance(
+            self.schema, Schema
+        ), "A Schema is required to be provided to GraphQLView."
+        assert not all((graphiql, batch)), "Use either graphiql or batch processing"
+
+    # noinspection PyUnusedLocal
+    def get_root_value(self, request):
+        return self.root_value
+
+    def get_middleware(self, request):
+        return self.middleware
+
+    def get_context(self, request):
+        return request
+
+    @classonlymethod
+    def as_view(cls, **initkwargs):
+        view = super().as_view(**initkwargs)
+        view._is_coroutine = coroutines._is_coroutine
+        return view
+
+    @method_decorator(ensure_csrf_cookie)
+    async def dispatch(self, request, *args, **kwargs):
+        try:
+            if request.method.lower() not in ("get", "post"):
+                raise HttpError(
+                    HttpResponseNotAllowed(
+                        ["GET", "POST"], "GraphQL only supports GET and POST requests."
+                    )
+                )
+
+            data = self.parse_body(request)
+            show_graphiql = self.graphiql and self.can_display_graphiql(request, data)
+
+            if show_graphiql:
+                return self.render_graphiql(
+                    request,
+                    # Dependency parameters.
+                    whatwg_fetch_version=self.whatwg_fetch_version,
+                    whatwg_fetch_sri=self.whatwg_fetch_sri,
+                    react_version=self.react_version,
+                    react_sri=self.react_sri,
+                    react_dom_sri=self.react_dom_sri,
+                    graphiql_version=self.graphiql_version,
+                    graphiql_sri=self.graphiql_sri,
+                    graphiql_css_sri=self.graphiql_css_sri,
+                    subscriptions_transport_ws_version=self.subscriptions_transport_ws_version,
+                    subscriptions_transport_ws_sri=self.subscriptions_transport_ws_sri,
+                    # The SUBSCRIPTION_PATH setting.
+                    subscription_path=self.subscription_path,
+                    # GraphiQL headers tab,
+                    graphiql_header_editor_enabled=graphene_settings.GRAPHIQL_HEADER_EDITOR_ENABLED,
+                    graphiql_should_persist_headers=graphene_settings.GRAPHIQL_SHOULD_PERSIST_HEADERS,
+                )
+
+            if self.batch:
+                responses = await gather(*[self.get_response(request, entry) for entry in data])
+                result = "[{}]".format(
+                    ",".join([response[0] for response in responses])
+                )
+                status_code = (
+                    responses
+                    and max(responses, key=lambda response: response[1])[1]
+                    or 200
+                )
+            else:
+                result, status_code = await self.get_response(request, data, show_graphiql)
+
+            return HttpResponse(
+                status=status_code, content=result, content_type="application/json"
+            )
+
+        except HttpError as e:
+            response = e.response
+            response["Content-Type"] = "application/json"
+            response.content = self.json_encode(
+                request, {"errors": [self.format_error(e)]}
+            )
+            return response
+
+    async def get_response(self, request, data, show_graphiql=False):
+        query, variables, operation_name, id = self.get_graphql_params(request, data)
+
+        execution_result = await self.execute_graphql_request(
+            request, data, query, variables, operation_name, show_graphiql
+        )
+
+        if getattr(request, MUTATION_ERRORS_FLAG, False) is True:
+            set_rollback()
+
+        status_code = 200
+        if execution_result:
+            response = {}
+
+            if execution_result.errors:
+                set_rollback()
+                response["errors"] = [
+                    self.format_error(e) for e in execution_result.errors
+                ]
+
+            if execution_result.errors and any(
+                not getattr(e, "path", None) for e in execution_result.errors
+            ):
+                status_code = 400
+            else:
+                response["data"] = execution_result.data
+
+            if self.batch:
+                response["id"] = id
+                response["status"] = status_code
+
+            result = self.json_encode(request, response, pretty=show_graphiql)
+        else:
+            result = None
+
+        return result, status_code
+
+    def render_graphiql(self, request, **data):
+        return render(request, self.graphiql_template, data)
+
+    def json_encode(self, request, d, pretty=False):
+        if not (self.pretty or pretty) and not request.GET.get("pretty"):
+            return json.dumps(d, separators=(",", ":"))
+
+        return json.dumps(d, sort_keys=True, indent=2, separators=(",", ": "))
+
+    def parse_body(self, request):
+        content_type = self.get_content_type(request)
+
+        if content_type == "application/graphql":
+            return {"query": request.body.decode()}
+
+        elif content_type == "application/json":
+            # noinspection PyBroadException
+            try:
+                body = request.body.decode("utf-8")
+            except Exception as e:
+                raise HttpError(HttpResponseBadRequest(str(e)))
+
+            try:
+                request_json = json.loads(body)
+                if self.batch:
+                    assert isinstance(request_json, list), (
+                        "Batch requests should receive a list, but received {}."
+                    ).format(repr(request_json))
+                    assert (
+                        len(request_json) > 0
+                    ), "Received an empty list in the batch request."
+                else:
+                    assert isinstance(
+                        request_json, dict
+                    ), "The received data is not a valid JSON query."
+                return request_json
+            except AssertionError as e:
+                raise HttpError(HttpResponseBadRequest(str(e)))
+            except (TypeError, ValueError):
+                raise HttpError(HttpResponseBadRequest("POST body sent invalid JSON."))
+
+        elif content_type in [
+            "application/x-www-form-urlencoded",
+            "multipart/form-data",
+        ]:
+            return request.POST
+
+        return {}
+
+    async def execute_graphql_request(
+        self, request, data, query, variables, operation_name, show_graphiql=False
+    ):
+        if not query:
+            if show_graphiql:
+                return None
+            raise HttpError(HttpResponseBadRequest("Must provide query string."))
+
+        try:
+            document = parse(query)
+        except Exception as e:
+            return ExecutionResult(errors=[e])
+
+        if request.method.lower() == "get":
+            operation_ast = get_operation_ast(document, operation_name)
+            if operation_ast and operation_ast.operation != OperationType.QUERY:
+                if show_graphiql:
+                    return None
+
+                raise HttpError(
+                    HttpResponseNotAllowed(
+                        ["POST"],
+                        "Can only perform a {} operation from a POST request.".format(
+                            operation_ast.operation.value
+                        ),
+                    )
+                )
+
+        validation_errors = validate(self.schema.graphql_schema, document)
+        if validation_errors:
+            return ExecutionResult(data=None, errors=validation_errors)
+
+        try:
+            extra_options = {}
+            if self.execution_context_class:
+                extra_options["execution_context_class"] = self.execution_context_class
+
+            options = {
+                "source": query,
+                "root_value": self.get_root_value(request),
+                "variable_values": variables,
+                "operation_name": operation_name,
+                "context_value": self.get_context(request),
+                "middleware": self.get_middleware(request),
+            }
+            options.update(extra_options)
+
+            operation_ast = get_operation_ast(document, operation_name)
+            if (
+                operation_ast
+                and operation_ast.operation == OperationType.MUTATION
+                and (
+                    graphene_settings.ATOMIC_MUTATIONS is True
+                    or connection.settings_dict.get("ATOMIC_MUTATIONS", False) is True
+                )
+            ):
+                with transaction.atomic():
+                    result = await self.schema.execute_async(**options)
+                    if getattr(request, MUTATION_ERRORS_FLAG, False) is True:
+                        transaction.set_rollback(True)
+                return result
+
+            return await self.schema.execute_async(**options)
+        except Exception as e:
+            return ExecutionResult(errors=[e])
+
+    @classmethod
+    def can_display_graphiql(cls, request, data):
+        raw = "raw" in request.GET or "raw" in data
+        return not raw and cls.request_wants_html(request)
+
+    @classmethod
+    def request_wants_html(cls, request):
+        accepted = get_accepted_content_types(request)
+        accepted_length = len(accepted)
+        # the list will be ordered in preferred first - so we have to make
+        # sure the most preferred gets the highest number
+        html_priority = (
+            accepted_length - accepted.index("text/html")
+            if "text/html" in accepted
+            else 0
+        )
+        json_priority = (
+            accepted_length - accepted.index("application/json")
+            if "application/json" in accepted
+            else 0
+        )
+
+        return html_priority > json_priority
+
+    @staticmethod
+    def get_graphql_params(request, data):
+        query = request.GET.get("query") or data.get("query")
+        variables = request.GET.get("variables") or data.get("variables")
+        id = request.GET.get("id") or data.get("id")
+
+        if variables and isinstance(variables, str):
+            try:
+                variables = json.loads(variables)
+            except Exception:
+                raise HttpError(HttpResponseBadRequest("Variables are invalid JSON."))
+
+        operation_name = request.GET.get("operationName") or data.get("operationName")
+        if operation_name == "null":
+            operation_name = None
+
+        return query, variables, operation_name, id
+
+    @staticmethod
+    def format_error(error):
+        if isinstance(error, GraphQLError):
+            return error.formatted
+
+        return {"message": str(error)}
+
+    @staticmethod
+    def get_content_type(request):
+        meta = request.META
+        content_type = meta.get("CONTENT_TYPE", meta.get("HTTP_CONTENT_TYPE", ""))
+        return content_type.split(";", 1)[0].lower()

From 7ddaf9f5e683d31a5e9907c80e57e83e4c332bfd Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 31 Mar 2023 11:28:57 -0700
Subject: [PATCH 06/35] Handle coroutine results from resolvers in connections
 and filter connections

---
 graphene_django/fields.py        | 14 ++++++++++++++
 graphene_django/filter/fields.py | 12 ++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 68db6a017..c8899de2f 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -239,6 +239,20 @@ def connection_resolver(
 
         iterable = resolver(root, info, **args)
         
+        if info.is_awaitable(iterable):
+            async def resolve_connection_async():
+                iterable = await iterable
+                if iterable is None:
+                    iterable = default_manager
+                ## This could also be async
+                iterable = queryset_resolver(connection, iterable, info, args)
+                
+                if info.is_awaitable(iterable):
+                    iterable = await iterable
+                
+                return await sync_to_async(cls.resolve_connection)(connection, args, iterable, max_limit=max_limit)
+            return resolve_connection_async()
+        
         if iterable is None:
             iterable = default_manager
         # thus the iterable gets refiltered by resolve_queryset
diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py
index cdb8f850f..a14146058 100644
--- a/graphene_django/filter/fields.py
+++ b/graphene_django/filter/fields.py
@@ -7,6 +7,8 @@
 from graphene.types.argument import to_arguments
 from graphene.utils.str_converters import to_snake_case
 
+from asgiref.sync import sync_to_async
+
 from ..fields import DjangoConnectionField
 from .utils import get_filtering_args_from_filterset, get_filterset_class
 
@@ -92,6 +94,16 @@ def filter_kwargs():
 
         qs = super().resolve_queryset(connection, iterable, info, args)
 
+        if info.is_awaitable(qs):
+            async def filter_async():
+                filterset = filterset_class(
+                    data=filter_kwargs(), queryset=await qs, request=info.context
+                )
+                if await sync_to_async(filterset.is_valid)():
+                    return filterset.qs
+                raise ValidationError(filterset.form.errors.as_json())
+            return filter_async()
+
         filterset = filterset_class(
             data=filter_kwargs(), queryset=qs, request=info.context
         )

From ebbc5784357be404ec58de39351280cd2afed314 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 31 Mar 2023 11:48:11 -0700
Subject: [PATCH 07/35] Strange scope

---
 graphene_django/fields.py        | 4 ++--
 graphene_django/filter/fields.py | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index c8899de2f..6e1b0b1de 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -240,7 +240,7 @@ def connection_resolver(
         iterable = resolver(root, info, **args)
         
         if info.is_awaitable(iterable):
-            async def resolve_connection_async():
+            async def resolve_connection_async(iterable):
                 iterable = await iterable
                 if iterable is None:
                     iterable = default_manager
@@ -251,7 +251,7 @@ async def resolve_connection_async():
                     iterable = await iterable
                 
                 return await sync_to_async(cls.resolve_connection)(connection, args, iterable, max_limit=max_limit)
-            return resolve_connection_async()
+            return resolve_connection_async(iterable)
         
         if iterable is None:
             iterable = default_manager
diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py
index a14146058..c62ee9ca9 100644
--- a/graphene_django/filter/fields.py
+++ b/graphene_django/filter/fields.py
@@ -95,14 +95,14 @@ def filter_kwargs():
         qs = super().resolve_queryset(connection, iterable, info, args)
 
         if info.is_awaitable(qs):
-            async def filter_async():
+            async def filter_async(qs):
                 filterset = filterset_class(
                     data=filter_kwargs(), queryset=await qs, request=info.context
                 )
                 if await sync_to_async(filterset.is_valid)():
                     return filterset.qs
                 raise ValidationError(filterset.form.errors.as_json())
-            return filter_async()
+            return filter_async(qs)
 
         filterset = filterset_class(
             data=filter_kwargs(), queryset=qs, request=info.context

From 66938e9e291d48b73f81f3bdb108bcc46f7a0317 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 31 Mar 2023 11:49:16 -0700
Subject: [PATCH 08/35] async hates csrf

---
 graphene_django/views.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/graphene_django/views.py b/graphene_django/views.py
index 1ea6db8ae..a98a8c50d 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -485,7 +485,6 @@ def as_view(cls, **initkwargs):
         view._is_coroutine = coroutines._is_coroutine
         return view
 
-    @method_decorator(ensure_csrf_cookie)
     async def dispatch(self, request, *args, **kwargs):
         try:
             if request.method.lower() not in ("get", "post"):

From 1b2d5e02e42849c65432425a23026e61b893aaa6 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 31 Mar 2023 14:04:37 -0700
Subject: [PATCH 09/35] handle async serlizer mutations

---
 graphene_django/rest_framework/mutation.py | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py
index 4062a4423..8cdd4701c 100644
--- a/graphene_django/rest_framework/mutation.py
+++ b/graphene_django/rest_framework/mutation.py
@@ -2,6 +2,8 @@
 
 from django.shortcuts import get_object_or_404
 from rest_framework import serializers
+from asyncio import get_running_loop
+from asgiref.sync import sync_to_async
 
 import graphene
 from graphene.relay.mutation import ClientIDMutation
@@ -152,6 +154,19 @@ def mutate_and_get_payload(cls, root, info, **input):
         kwargs = cls.get_serializer_kwargs(root, info, **input)
         serializer = cls._meta.serializer_class(**kwargs)
 
+        try: 
+            get_running_loop()
+        except RuntimeError:
+                pass
+        else:
+            async def perform_mutate_async():
+                if await sync_to_async(serializer.is_valid)():
+                    return await sync_to_async(cls.perform_mutate)(serializer, info)
+                else:
+                    errors = ErrorType.from_errors(serializer.errors)
+                    return cls(errors=errors)
+            return perform_mutate_async()
+
         if serializer.is_valid():
             return cls.perform_mutate(serializer, info)
         else:

From 0a84a6ea38455906f2e978863a1503a272687f4b Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Sat, 1 Apr 2023 19:16:12 -0700
Subject: [PATCH 10/35] Handle async get_node

---
 graphene_django/converter.py | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/graphene_django/converter.py b/graphene_django/converter.py
index dbd7cb8db..90c2128df 100644
--- a/graphene_django/converter.py
+++ b/graphene_django/converter.py
@@ -265,8 +265,21 @@ def dynamic_type():
         _type = registry.get_type_for_model(model)
         if not _type:
             return
+        
+        class CustomField(Field):
+            def wrap_resolve(self, parent_resolver):
+                resolver = super().wrap_resolve(parent_resolver)
 
-        return Field(_type, required=not field.null)
+                try: 
+                    get_running_loop()
+                except RuntimeError:
+                    pass
+                else:
+                    resolver=sync_to_async(resolver)
+
+                return resolver
+
+        return CustomField(_type, required=not field.null)
 
     return Dynamic(dynamic_type)
 

From 64d311d770a2c171300854fc8dd64f33b111fa0d Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Sat, 1 Apr 2023 19:54:50 -0700
Subject: [PATCH 11/35] Copy tests for query to test async execution

---
 graphene_django/tests/test_query_async.py | 1772 +++++++++++++++++++++
 setup.py                                  |    1 +
 2 files changed, 1773 insertions(+)
 create mode 100644 graphene_django/tests/test_query_async.py

diff --git a/graphene_django/tests/test_query_async.py b/graphene_django/tests/test_query_async.py
new file mode 100644
index 000000000..44eaeb6de
--- /dev/null
+++ b/graphene_django/tests/test_query_async.py
@@ -0,0 +1,1772 @@
+import datetime
+import base64
+
+from django.db import models
+from django.db.models import Q
+from django.utils.functional import SimpleLazyObject
+from graphql_relay import to_global_id
+from pytest import raises, mark
+from asgiref.sync import sync_to_async
+
+import graphene
+from graphene.relay import Node
+
+from ..compat import IntegerRangeField, MissingType
+from ..fields import DjangoConnectionField
+from ..types import DjangoObjectType
+from ..utils import DJANGO_FILTER_INSTALLED
+from .models import Article, CNNReporter, Film, FilmDetails, Person, Pet, Reporter
+
+@mark.asyncio
+async def test_should_query_only_fields():
+    with raises(Exception):
+
+        class ReporterType(DjangoObjectType):
+            class Meta:
+                model = Reporter
+                fields = ("articles",)
+
+        schema = graphene.Schema(query=ReporterType)
+        query = """
+            query ReporterQuery {
+              articles
+            }
+        """
+        result = await schema.execute_async(query)
+        assert not result.errors
+
+@mark.asyncio
+async def test_should_query_simplelazy_objects():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            fields = ("id",)
+
+    class Query(graphene.ObjectType):
+        reporter = graphene.Field(ReporterType)
+
+        def resolve_reporter(self, info):
+            return SimpleLazyObject(lambda: Reporter(id=1))
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query {
+          reporter {
+            id
+          }
+        }
+    """
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == {"reporter": {"id": "1"}}
+
+@mark.asyncio
+async def test_should_query_wrapped_simplelazy_objects():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            fields = ("id",)
+
+    class Query(graphene.ObjectType):
+        reporter = graphene.Field(ReporterType)
+
+        def resolve_reporter(self, info):
+            return SimpleLazyObject(lambda: SimpleLazyObject(lambda: Reporter(id=1)))
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query {
+          reporter {
+            id
+          }
+        }
+    """
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == {"reporter": {"id": "1"}}
+
+@mark.asyncio
+async def test_should_query_well():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        reporter = graphene.Field(ReporterType)
+
+        def resolve_reporter(self, info):
+            return Reporter(first_name="ABA", last_name="X")
+
+    query = """
+        query ReporterQuery {
+          reporter {
+            firstName,
+            lastName,
+            email
+          }
+        }
+    """
+    expected = {"reporter": {"firstName": "ABA", "lastName": "X", "email": ""}}
+    schema = graphene.Schema(query=Query)
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+@mark.skipif(IntegerRangeField is MissingType, reason="RangeField should exist")
+async def test_should_query_postgres_fields():
+    from django.contrib.postgres.fields import (
+        IntegerRangeField,
+        ArrayField,
+        JSONField,
+        HStoreField,
+    )
+
+    class Event(models.Model):
+        ages = IntegerRangeField(help_text="The age ranges")
+        data = JSONField(help_text="Data")
+        store = HStoreField()
+        tags = ArrayField(models.CharField(max_length=50))
+
+    class EventType(DjangoObjectType):
+        class Meta:
+            model = Event
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        event = graphene.Field(EventType)
+
+        def resolve_event(self, info):
+            return Event(
+                ages=(0, 10),
+                data={"angry_babies": True},
+                store={"h": "store"},
+                tags=["child", "angry", "babies"],
+            )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query myQuery {
+          event {
+            ages
+            tags
+            data
+            store
+          }
+        }
+    """
+    expected = {
+        "event": {
+            "ages": [0, 10],
+            "tags": ["child", "angry", "babies"],
+            "data": '{"angry_babies": true}',
+            "store": '{"h": "store"}',
+        }
+    }
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_node():
+    class ReporterNode(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+        @classmethod
+        def get_node(cls, info, id):
+            return Reporter(id=2, first_name="Cookie Monster")
+
+        def resolve_articles(self, info, **args):
+            return [Article(headline="Hi!")]
+
+    class ArticleNode(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            fields = "__all__"
+
+        @classmethod
+        def get_node(cls, info, id):
+            return Article(
+                id=1, headline="Article node", pub_date=datetime.date(2002, 3, 11)
+            )
+
+    class Query(graphene.ObjectType):
+        node = Node.Field()
+        reporter = graphene.Field(ReporterNode)
+        article = graphene.Field(ArticleNode)
+
+        def resolve_reporter(self, info):
+            return Reporter(id=1, first_name="ABA", last_name="X")
+
+    query = """
+        query ReporterQuery {
+          reporter {
+            id,
+            firstName,
+            articles {
+              edges {
+                node {
+                  headline
+                }
+              }
+            }
+            lastName,
+            email
+          }
+          myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
+            id
+            ... on ReporterNode {
+                firstName
+            }
+            ... on ArticleNode {
+                headline
+                pubDate
+            }
+          }
+        }
+    """
+    expected = {
+        "reporter": {
+            "id": "UmVwb3J0ZXJOb2RlOjE=",
+            "firstName": "ABA",
+            "lastName": "X",
+            "email": "",
+            "articles": {"edges": [{"node": {"headline": "Hi!"}}]},
+        },
+        "myArticle": {
+            "id": "QXJ0aWNsZU5vZGU6MQ==",
+            "headline": "Article node",
+            "pubDate": "2002-03-11",
+        },
+    }
+    schema = graphene.Schema(query=Query)
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_query_onetoone_fields():
+    film = Film.objects.create(id=1)
+    film_details = FilmDetails.objects.create(id=1, film=film)
+
+    class FilmNode(DjangoObjectType):
+        class Meta:
+            model = Film
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class FilmDetailsNode(DjangoObjectType):
+        class Meta:
+            model = FilmDetails
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        film = graphene.Field(FilmNode)
+        film_details = graphene.Field(FilmDetailsNode)
+
+        def resolve_film(root, info):
+            return film
+
+        def resolve_film_details(root, info):
+            return film_details
+
+    query = """
+        query FilmQuery {
+          filmDetails {
+            id
+            film {
+              id
+            }
+          }
+          film {
+            id
+            details {
+              id
+            }
+          }
+        }
+    """
+    expected = {
+        "filmDetails": {
+            "id": "RmlsbURldGFpbHNOb2RlOjE=",
+            "film": {"id": "RmlsbU5vZGU6MQ=="},
+        },
+        "film": {
+            "id": "RmlsbU5vZGU6MQ==",
+            "details": {"id": "RmlsbURldGFpbHNOb2RlOjE="},
+        },
+    }
+    schema = graphene.Schema(query=Query)
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_query_connectionfields():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = ("articles",)
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+        def resolve_all_reporters(self, info, **args):
+            return [Reporter(id=1)]
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterConnectionQuery {
+          allReporters {
+            pageInfo {
+              hasNextPage
+            }
+            edges {
+              node {
+                id
+              }
+            }
+          }
+        }
+    """
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == {
+        "allReporters": {
+            "pageInfo": {"hasNextPage": False},
+            "edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}],
+        }
+    }
+
+@mark.asyncio
+async def test_should_keep_annotations():
+    from django.db.models import Count, Avg
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = ("articles",)
+
+    class ArticleType(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            fields = "__all__"
+            filter_fields = ("lang",)
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+        all_articles = DjangoConnectionField(ArticleType)
+
+        @staticmethod
+        @sync_to_async
+        def resolve_all_reporters(self, info, **args):
+            return Reporter.objects.annotate(articles_c=Count("articles")).order_by(
+                "articles_c"
+            )
+
+        @staticmethod
+        @sync_to_async
+        def resolve_all_articles(self, info, **args):
+            return Article.objects.annotate(import_avg=Avg("importance")).order_by(
+                "import_avg"
+            )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterConnectionQuery {
+          allReporters {
+            pageInfo {
+              hasNextPage
+            }
+            edges {
+              node {
+                id
+              }
+            }
+          }
+          allArticles {
+            pageInfo {
+              hasNextPage
+            }
+            edges {
+              node {
+                id
+              }
+            }
+          }
+        }
+    """
+    result = await schema.execute_async(query)
+    assert not result.errors
+
+
+@mark.skipif(
+    not DJANGO_FILTER_INSTALLED, reason="django-filter should be installed")
+@mark.asyncio
+async def test_should_query_node_filtering():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class ArticleType(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            fields = "__all__"
+            filter_fields = ("lang",)
+            convert_choices_to_enum = False
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+    Article.objects.create(
+        headline="Article Node 1",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="es",
+    )
+    Article.objects.create(
+        headline="Article Node 2",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="en",
+    )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query NodeFilteringQuery {
+            allReporters {
+                edges {
+                    node {
+                        id
+                        articles(lang: "es") {
+                            edges {
+                                node {
+                                    id
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {
+        "allReporters": {
+            "edges": [
+                {
+                    "node": {
+                        "id": "UmVwb3J0ZXJUeXBlOjE=",
+                        "articles": {
+                            "edges": [{"node": {"id": "QXJ0aWNsZVR5cGU6MQ=="}}]
+                        },
+                    }
+                }
+            ]
+        }
+    }
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+
+@mark.skipif(
+    not DJANGO_FILTER_INSTALLED, reason="django-filter should be installed")
+@mark.asyncio
+async def test_should_query_node_filtering_with_distinct_queryset():
+    class FilmType(DjangoObjectType):
+        class Meta:
+            model = Film
+            interfaces = (Node,)
+            fields = "__all__"
+            filter_fields = ("genre",)
+
+    class Query(graphene.ObjectType):
+        films = DjangoConnectionField(FilmType)
+
+        # def resolve_all_reporters_with_berlin_films(self, args, context, info):
+        #    return Reporter.objects.filter(Q(films__film__location__contains="Berlin") | Q(a_choice=1))
+
+        @sync_to_async
+        def resolve_films(self, info, **args):
+            return Film.objects.filter(
+                Q(details__location__contains="Berlin") | Q(genre__in=["ot"])
+            ).distinct()
+
+    f = Film.objects.create()
+    fd = FilmDetails.objects.create(location="Berlin", film=f)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query NodeFilteringQuery {
+            films {
+                edges {
+                    node {
+                        genre
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"films": {"edges": [{"node": {"genre": "OT"}}]}}
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+
+@mark.skipif(not DJANGO_FILTER_INSTALLED, reason="django-filter should be installed")
+@mark.asyncio
+async def test_should_query_node_multiple_filtering():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class ArticleType(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            fields = "__all__"
+            filter_fields = ("lang", "headline")
+            convert_choices_to_enum = False
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+    Article.objects.create(
+        headline="Article Node 1",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="es",
+    )
+    Article.objects.create(
+        headline="Article Node 2",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="es",
+    )
+    Article.objects.create(
+        headline="Article Node 3",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="en",
+    )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query NodeFilteringQuery {
+            allReporters {
+                edges {
+                    node {
+                        id
+                        articles(lang: "es", headline: "Article Node 1") {
+                            edges {
+                                node {
+                                    id
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {
+        "allReporters": {
+            "edges": [
+                {
+                    "node": {
+                        "id": "UmVwb3J0ZXJUeXBlOjE=",
+                        "articles": {
+                            "edges": [{"node": {"id": "QXJ0aWNsZVR5cGU6MQ=="}}]
+                        },
+                    }
+                }
+            ]
+        }
+    }
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_enforce_first_or_last(graphene_settings):
+    graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = True
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query NodeFilteringQuery {
+            allReporters {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"allReporters": None}
+
+    result = await schema.execute_async(query)
+    assert len(result.errors) == 1
+    assert str(result.errors[0]).startswith(
+        "You must provide a `first` or `last` value to properly "
+        "paginate the `allReporters` connection.\n"
+    )
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_error_if_first_is_greater_than_max(graphene_settings):
+    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    assert Query.all_reporters.max_limit == 100
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query NodeFilteringQuery {
+            allReporters(first: 101) {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"allReporters": None}
+
+    result = await schema.execute_async(query)
+    assert len(result.errors) == 1
+    assert str(result.errors[0]).startswith(
+        "Requesting 101 records on the `allReporters` connection "
+        "exceeds the `first` limit of 100 records.\n"
+    )
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_error_if_last_is_greater_than_max(graphene_settings):
+    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    assert Query.all_reporters.max_limit == 100
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query NodeFilteringQuery {
+            allReporters(last: 101) {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"allReporters": None}
+
+    result = await schema.execute_async(query)
+    assert len(result.errors) == 1
+    assert str(result.errors[0]).startswith(
+        "Requesting 101 records on the `allReporters` connection "
+        "exceeds the `last` limit of 100 records.\n"
+    )
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_query_promise_connectionfields():
+    from promise import Promise
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+        def resolve_all_reporters(self, info, **args):
+            return Promise.resolve([Reporter(id=1)]).get()
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery {
+            allReporters(first: 1) {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_query_connectionfields_with_last():
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+        def resolve_all_reporters(self, info, **args):
+            return Reporter.objects.all()
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterLastQuery {
+            allReporters(last: 1) {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_query_connectionfields_with_manager():
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="NotDoe", email="johndoe@example.com", a_choice=1
+    )
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType, on="doe_objects")
+
+        def resolve_all_reporters(self, info, **args):
+            return Reporter.objects.all()
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterLastQuery {
+            allReporters(first: 1) {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_query_dataloader_fields():
+    from promise import Promise
+    from promise.dataloader import DataLoader
+
+    def article_batch_load_fn(keys):
+        queryset = Article.objects.filter(reporter_id__in=keys)
+        return Promise.resolve(
+            [
+                [article for article in queryset if article.reporter_id == id]
+                for id in keys
+            ]
+        )
+
+    article_loader = DataLoader(article_batch_load_fn)
+
+    class ArticleType(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            use_connection = True
+            fields = "__all__"
+
+        articles = DjangoConnectionField(ArticleType)
+
+        def resolve_articles(self, info, **args):
+            return article_loader.load(self.id).get()
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    Article.objects.create(
+        headline="Article Node 1",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="es",
+    )
+    Article.objects.create(
+        headline="Article Node 2",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="en",
+    )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery {
+            allReporters(first: 1) {
+                edges {
+                    node {
+                        id
+                        articles(first: 2) {
+                            edges {
+                                node {
+                                    headline
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {
+        "allReporters": {
+            "edges": [
+                {
+                    "node": {
+                        "id": "UmVwb3J0ZXJUeXBlOjE=",
+                        "articles": {
+                            "edges": [
+                                {"node": {"headline": "Article Node 1"}},
+                                {"node": {"headline": "Article Node 2"}},
+                            ]
+                        },
+                    }
+                }
+            ]
+        }
+    }
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_handle_inherited_choices():
+    class BaseModel(models.Model):
+        choice_field = models.IntegerField(choices=((0, "zero"), (1, "one")))
+
+    class ChildModel(BaseModel):
+        class Meta:
+            proxy = True
+
+    class BaseType(DjangoObjectType):
+        class Meta:
+            model = BaseModel
+            fields = "__all__"
+
+    class ChildType(DjangoObjectType):
+        class Meta:
+            model = ChildModel
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        base = graphene.Field(BaseType)
+        child = graphene.Field(ChildType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query {
+          child {
+            choiceField
+          }
+        }
+    """
+    result = await schema.execute_async(query)
+    assert not result.errors
+
+@mark.asyncio
+async def test_proxy_model_support():
+    """
+    This test asserts that we can query for all Reporters and proxied Reporters.
+    """
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            use_connection = True
+            fields = "__all__"
+
+    class CNNReporterType(DjangoObjectType):
+        class Meta:
+            model = CNNReporter
+            interfaces = (Node,)
+            use_connection = True
+            fields = "__all__"
+
+    reporter = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    cnn_reporter = CNNReporter.objects.create(
+        first_name="Some",
+        last_name="Guy",
+        email="someguy@cnn.com",
+        a_choice=1,
+        reporter_type=2,  # set this guy to be CNN
+    )
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+        cnn_reporters = DjangoConnectionField(CNNReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ProxyModelQuery {
+            allReporters {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+            cnnReporters {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {
+        "allReporters": {
+            "edges": [
+                {"node": {"id": to_global_id("ReporterType", reporter.id)}},
+                {"node": {"id": to_global_id("ReporterType", cnn_reporter.id)}},
+            ]
+        },
+        "cnnReporters": {
+            "edges": [
+                {"node": {"id": to_global_id("CNNReporterType", cnn_reporter.id)}}
+            ]
+        },
+    }
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_resolve_get_queryset_connectionfields():
+    reporter_1 = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+    reporter_2 = CNNReporter.objects.create(
+        first_name="Some",
+        last_name="Guy",
+        email="someguy@cnn.com",
+        a_choice=1,
+        reporter_type=2,  # set this guy to be CNN
+    )
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+        @classmethod
+        def get_queryset(cls, queryset, info):
+            return queryset.filter(reporter_type=2)
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery {
+            allReporters(first: 1) {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjI="}}]}}
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert result.data == expected
+
+@mark.asyncio
+async def test_connection_should_limit_after_to_list_length():
+    reporter_1 = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+    reporter_2 = Reporter.objects.create(
+        first_name="Some", last_name="Guy", email="someguy@cnn.com", a_choice=1
+    )
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery ($after: String) {
+            allReporters(first: 1 after: $after) {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    after = base64.b64encode(b"arrayconnection:10").decode()
+    result = await schema.execute_async(query, variable_values=dict(after=after))
+    expected = {"allReporters": {"edges": []}}
+    assert not result.errors
+    assert result.data == expected
+
+
+REPORTERS = [
+    dict(
+        first_name=f"First {i}",
+        last_name=f"Last {i}",
+        email=f"johndoe+{i}@example.com",
+        a_choice=1,
+    )
+    for i in range(6)
+]
+
+@mark.asyncio
+async def test_should_return_max_limit(graphene_settings):
+    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 4
+    reporters = [Reporter(**kwargs) for kwargs in REPORTERS]
+    Reporter.objects.bulk_create(reporters)
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query AllReporters {
+            allReporters {
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    assert len(result.data["allReporters"]["edges"]) == 4
+
+@mark.asyncio
+async def test_should_have_next_page(graphene_settings):
+    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 4
+    reporters = [Reporter(**kwargs) for kwargs in REPORTERS]
+    await Reporter.objects.abulk_create(reporters)
+    db_reporters = await sync_to_async(Reporter.objects.all)()
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query AllReporters($first: Int, $after: String) {
+            allReporters(first: $first, after: $after) {
+                pageInfo {
+                    hasNextPage
+                    endCursor
+                }
+                edges {
+                    node {
+                        id
+                    }
+                }
+            }
+        }
+    """
+
+    result = await schema.execute_async(query, variable_values={})
+    assert not result.errors
+    assert len(result.data["allReporters"]["edges"]) == 4
+    assert result.data["allReporters"]["pageInfo"]["hasNextPage"]
+
+    last_result = result.data["allReporters"]["pageInfo"]["endCursor"]
+    result2 = await schema.execute_async(query, variable_values=dict(first=4, after=last_result))
+    assert not result2.errors
+    assert len(result2.data["allReporters"]["edges"]) == 2
+    assert not result2.data["allReporters"]["pageInfo"]["hasNextPage"]
+    gql_reporters = (
+        result.data["allReporters"]["edges"] + result2.data["allReporters"]["edges"]
+    )
+
+    def get_test():    
+        assert {to_global_id("ReporterType", reporter.id) for reporter in db_reporters} == {
+            gql_reporter["node"]["id"] for gql_reporter in gql_reporters
+        }
+    await sync_to_async(get_test)()
+
+@mark.parametrize("max_limit", [100, 4])
+class TestBackwardPagination:
+    def setup_schema(self, graphene_settings, max_limit):
+        graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
+        reporters = [Reporter(**kwargs) for kwargs in REPORTERS]
+        Reporter.objects.bulk_create(reporters)
+
+        class ReporterType(DjangoObjectType):
+            class Meta:
+                model = Reporter
+                interfaces = (Node,)
+                fields = "__all__"
+
+        class Query(graphene.ObjectType):
+            all_reporters = DjangoConnectionField(ReporterType)
+
+        schema = graphene.Schema(query=Query)
+        return schema
+    @mark.asyncio
+    async def test_query_last(self, graphene_settings, max_limit):
+        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
+        query_last = """
+            query {
+                allReporters(last: 3) {
+                    edges {
+                        node {
+                            firstName
+                        }
+                    }
+                }
+            }
+        """
+
+        result = await schema.execute_async(query_last)
+        assert not result.errors
+        assert len(result.data["allReporters"]["edges"]) == 3
+        assert [
+            e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
+        ] == ["First 3", "First 4", "First 5"]
+    @mark.asyncio
+    async def test_query_first_and_last(self, graphene_settings, max_limit):
+        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
+        query_first_and_last = """
+            query {
+                allReporters(first: 4, last: 3) {
+                    edges {
+                        node {
+                            firstName
+                        }
+                    }
+                }
+            }
+        """
+
+        result = await schema.execute_async(query_first_and_last)
+        assert not result.errors
+        assert len(result.data["allReporters"]["edges"]) == 3
+        assert [
+            e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
+        ] == ["First 1", "First 2", "First 3"]
+    @mark.asyncio
+    async def test_query_first_last_and_after(self, graphene_settings, max_limit):
+        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
+        query_first_last_and_after = """
+            query queryAfter($after: String) {
+                allReporters(first: 4, last: 3, after: $after) {
+                    edges {
+                        node {
+                            firstName
+                        }
+                    }
+                }
+            }
+        """
+
+        after = base64.b64encode(b"arrayconnection:0").decode()
+        result = await schema.execute_async(
+            query_first_last_and_after,
+            variable_values=dict(after=after),
+        )
+        assert not result.errors
+        assert len(result.data["allReporters"]["edges"]) == 3
+        assert [
+            e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
+        ] == ["First 2", "First 3", "First 4"]
+    @mark.asyncio
+    async def test_query_last_and_before(self, graphene_settings, max_limit):
+        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
+        query_first_last_and_after = """
+            query queryAfter($before: String) {
+                allReporters(last: 1, before: $before) {
+                    edges {
+                        node {
+                            firstName
+                        }
+                    }
+                }
+            }
+        """
+
+        result = await schema.execute_async(
+            query_first_last_and_after,
+        )
+        assert not result.errors
+        assert len(result.data["allReporters"]["edges"]) == 1
+        assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 5"
+
+        before = base64.b64encode(b"arrayconnection:5").decode()
+        result = await schema.execute_async(
+            query_first_last_and_after,
+            variable_values=dict(before=before),
+        )
+        assert not result.errors
+        assert len(result.data["allReporters"]["edges"]) == 1
+        assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 4"
+
+@mark.asyncio
+async def test_should_preserve_prefetch_related(django_assert_num_queries):
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (graphene.relay.Node,)
+            fields = "__all__"
+
+    class FilmType(DjangoObjectType):
+        reporters = DjangoConnectionField(ReporterType)
+
+        class Meta:
+            model = Film
+            interfaces = (graphene.relay.Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        films = DjangoConnectionField(FilmType)
+
+        def resolve_films(root, info, **kwargs):
+            qs = Film.objects.prefetch_related("reporters")
+            return qs
+
+    r1 = Reporter.objects.create(first_name="Dave", last_name="Smith")
+    r2 = Reporter.objects.create(first_name="Jane", last_name="Doe")
+
+    f1 = Film.objects.create()
+    f1.reporters.set([r1, r2])
+    f2 = Film.objects.create()
+    f2.reporters.set([r2])
+
+    query = """
+        query {
+            films {
+                edges {
+                    node {
+                        reporters {
+                            edges {
+                                node {
+                                    firstName
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    """
+    schema = graphene.Schema(query=Query)
+
+    with django_assert_num_queries(3) as captured:
+        result = await schema.execute_async(query)
+        assert not result.errors
+
+@mark.asyncio
+async def test_should_preserve_annotations():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (graphene.relay.Node,)
+            fields = "__all__"
+
+    class FilmType(DjangoObjectType):
+        reporters = DjangoConnectionField(ReporterType)
+        reporters_count = graphene.Int()
+
+        class Meta:
+            model = Film
+            interfaces = (graphene.relay.Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        films = DjangoConnectionField(FilmType)
+
+        def resolve_films(root, info, **kwargs):
+            qs = Film.objects.prefetch_related("reporters")
+            return qs.annotate(reporters_count=models.Count("reporters"))
+
+    r1 = Reporter.objects.create(first_name="Dave", last_name="Smith")
+    r2 = Reporter.objects.create(first_name="Jane", last_name="Doe")
+
+    f1 = Film.objects.create()
+    f1.reporters.set([r1, r2])
+    f2 = Film.objects.create()
+    f2.reporters.set([r2])
+
+    query = """
+        query {
+            films {
+                edges {
+                    node {
+                        reportersCount
+                    }
+                }
+            }
+        }
+    """
+    schema = graphene.Schema(query=Query)
+    result = await schema.execute_async(query)
+    assert not result.errors, str(result)
+
+    expected = {
+        "films": {
+            "edges": [{"node": {"reportersCount": 2}}, {"node": {"reportersCount": 1}}]
+        }
+    }
+    assert result.data == expected, str(result.data)
+    assert not result.errors
+
+@mark.asyncio
+async def test_connection_should_enable_offset_filtering():
+    Reporter.objects.create(first_name="John", last_name="Doe")
+    Reporter.objects.create(first_name="Some", last_name="Guy")
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query {
+            allReporters(first: 1, offset: 1) {
+                edges {
+                    node {
+                        firstName
+                        lastName
+                    }
+                }
+            }
+        }
+    """
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    expected = {
+        "allReporters": {
+            "edges": [
+                {"node": {"firstName": "Some", "lastName": "Guy"}},
+            ]
+        }
+    }
+    assert result.data == expected
+
+@mark.asyncio
+async def test_connection_should_enable_offset_filtering_higher_than_max_limit(
+    graphene_settings,
+):
+    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 2
+    Reporter.objects.create(first_name="John", last_name="Doe")
+    Reporter.objects.create(first_name="Some", last_name="Guy")
+    Reporter.objects.create(first_name="Jane", last_name="Roe")
+    Reporter.objects.create(first_name="Some", last_name="Lady")
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query {
+            allReporters(first: 1, offset: 3) {
+                edges {
+                    node {
+                        firstName
+                        lastName
+                    }
+                }
+            }
+        }
+    """
+
+    result = await schema.execute_async(query)
+    assert not result.errors
+    expected = {
+        "allReporters": {
+            "edges": [
+                {"node": {"firstName": "Some", "lastName": "Lady"}},
+            ]
+        }
+    }
+    assert result.data == expected
+
+@mark.asyncio
+async def test_connection_should_forbid_offset_filtering_with_before():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery ($before: String) {
+            allReporters(first: 1, before: $before, offset: 1) {
+                edges {
+                    node {
+                        firstName
+                        lastName
+                    }
+                }
+            }
+        }
+    """
+    before = base64.b64encode(b"arrayconnection:2").decode()
+    result = await schema.execute_async(query, variable_values=dict(before=before))
+    expected_error = "You can't provide a `before` value at the same time as an `offset` value to properly paginate the `allReporters` connection."
+    assert len(result.errors) == 1
+    assert result.errors[0].message == expected_error
+
+@mark.asyncio
+async def test_connection_should_allow_offset_filtering_with_after():
+    Reporter.objects.create(first_name="John", last_name="Doe")
+    Reporter.objects.create(first_name="Some", last_name="Guy")
+    Reporter.objects.create(first_name="Jane", last_name="Roe")
+    Reporter.objects.create(first_name="Some", last_name="Lady")
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery ($after: String) {
+            allReporters(first: 1, after: $after, offset: 1) {
+                edges {
+                    node {
+                        firstName
+                        lastName
+                    }
+                }
+            }
+        }
+    """
+
+    after = base64.b64encode(b"arrayconnection:0").decode()
+    result = await schema.execute_async(query, variable_values=dict(after=after))
+    assert not result.errors
+    expected = {
+        "allReporters": {
+            "edges": [
+                {"node": {"firstName": "Jane", "lastName": "Roe"}},
+            ]
+        }
+    }
+    assert result.data == expected
+
+@mark.asyncio
+async def test_connection_should_succeed_if_last_higher_than_number_of_objects():
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery ($last: Int) {
+            allReporters(last: $last) {
+                edges {
+                    node {
+                        firstName
+                        lastName
+                    }
+                }
+            }
+        }
+    """
+
+    result = await schema.execute_async(query, variable_values=dict(last=2))
+    assert not result.errors
+    expected = {"allReporters": {"edges": []}}
+    assert result.data == expected
+
+    Reporter.objects.create(first_name="John", last_name="Doe")
+    Reporter.objects.create(first_name="Some", last_name="Guy")
+    Reporter.objects.create(first_name="Jane", last_name="Roe")
+    Reporter.objects.create(first_name="Some", last_name="Lady")
+
+    result = await schema.execute_async(query, variable_values=dict(last=2))
+    assert not result.errors
+    expected = {
+        "allReporters": {
+            "edges": [
+                {"node": {"firstName": "Jane", "lastName": "Roe"}},
+                {"node": {"firstName": "Some", "lastName": "Lady"}},
+            ]
+        }
+    }
+    assert result.data == expected
+
+    result = await schema.execute_async(query, variable_values=dict(last=4))
+    assert not result.errors
+    expected = {
+        "allReporters": {
+            "edges": [
+                {"node": {"firstName": "John", "lastName": "Doe"}},
+                {"node": {"firstName": "Some", "lastName": "Guy"}},
+                {"node": {"firstName": "Jane", "lastName": "Roe"}},
+                {"node": {"firstName": "Some", "lastName": "Lady"}},
+            ]
+        }
+    }
+    assert result.data == expected
+
+    result = await schema.execute_async(query, variable_values=dict(last=20))
+    assert not result.errors
+    expected = {
+        "allReporters": {
+            "edges": [
+                {"node": {"firstName": "John", "lastName": "Doe"}},
+                {"node": {"firstName": "Some", "lastName": "Guy"}},
+                {"node": {"firstName": "Jane", "lastName": "Roe"}},
+                {"node": {"firstName": "Some", "lastName": "Lady"}},
+            ]
+        }
+    }
+    assert result.data == expected
+
+@mark.asyncio
+async def test_should_query_nullable_foreign_key():
+    class PetType(DjangoObjectType):
+        class Meta:
+            model = Pet
+
+    class PersonType(DjangoObjectType):
+        class Meta:
+            model = Person
+
+    class Query(graphene.ObjectType):
+        pet = graphene.Field(PetType, name=graphene.String(required=True))
+        person = graphene.Field(PersonType, name=graphene.String(required=True))
+        
+        @staticmethod
+        @sync_to_async
+        def resolve_pet(self, info, name):
+            return Pet.objects.filter(name=name).first()
+
+        @staticmethod
+        @sync_to_async
+        def resolve_person(self, info, name):
+            return Person.objects.filter(name=name).first()
+
+    schema = graphene.Schema(query=Query)
+
+    person = await Person.objects.acreate(name="Jane")
+    pets = [
+        await Pet.objects.acreate(name="Stray dog", age=1),
+        await Pet.objects.acreate(name="Jane's dog", owner=person, age=1),
+    ]
+
+    query_pet = """
+        query getPet($name: String!) {
+            pet(name: $name) {
+                owner {
+                    name
+                }
+            }
+        }
+    """
+    result = await schema.execute_async(query_pet, variables={"name": "Stray dog"})
+    assert not result.errors
+    assert result.data["pet"] == {
+        "owner": None,
+    }
+
+    result = await schema.execute_async(query_pet, variables={"name": "Jane's dog"})
+    assert not result.errors
+    assert result.data["pet"] == {
+        "owner": {"name": "Jane"},
+    }
+
+    query_owner = """
+        query getOwner($name: String!) {
+            person(name: $name) {
+                pets {
+                    name
+                }
+            }
+        }
+    """
+    result = await schema.execute_async(query_owner, variables={"name": "Jane"})
+    assert not result.errors
+    assert result.data["person"] == {
+        "pets": [{"name": "Jane's dog"}],
+    }
diff --git a/setup.py b/setup.py
index 37b57a839..64273e2a8 100644
--- a/setup.py
+++ b/setup.py
@@ -22,6 +22,7 @@
     "pytz",
     "django-filter>=22.1",
     "pytest-django>=4.5.2",
+    "pytest-asyncio>=0.16,<2"
 ] + rest_framework_require
 
 

From bdb8e8444629fbd22ff21580bb506d54a925746f Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 4 May 2023 15:26:33 +0100
Subject: [PATCH 12/35] Update cookbook for async testing

---
 examples/cookbook/cookbook/recipes/schema.py | 32 +++++++++++++++++++-
 examples/cookbook/cookbook/urls.py           |  6 ++--
 2 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/examples/cookbook/cookbook/recipes/schema.py b/examples/cookbook/cookbook/recipes/schema.py
index ea5ed38f1..82f7f1d6e 100644
--- a/examples/cookbook/cookbook/recipes/schema.py
+++ b/examples/cookbook/cookbook/recipes/schema.py
@@ -1,16 +1,40 @@
+import asyncio
+
+from asgiref.sync import sync_to_async
+
 from cookbook.recipes.models import Recipe, RecipeIngredient
-from graphene import Node
+from graphene import Node, String, Field
 from graphene_django.filter import DjangoFilterConnectionField
 from graphene_django.types import DjangoObjectType
 
 
 class RecipeNode(DjangoObjectType):
+    async_field = String()
+
     class Meta:
         model = Recipe
         interfaces = (Node,)
         fields = "__all__"
         filter_fields = ["title", "amounts"]
 
+    async def resolve_async_field(self, info):
+        await asyncio.sleep(2)
+        return "success"
+
+
+class RecipeType(DjangoObjectType):
+    async_field = String()
+
+    class Meta:
+        model = Recipe
+        fields = "__all__"
+        filter_fields = ["title", "amounts"]
+        skip_registry = True
+
+    async def resolve_async_field(self, info):
+        await asyncio.sleep(2)
+        return "success"
+
 
 class RecipeIngredientNode(DjangoObjectType):
     class Meta:
@@ -27,7 +51,13 @@ class Meta:
 
 class Query:
     recipe = Node.Field(RecipeNode)
+    raw_recipe = Field(RecipeType)
     all_recipes = DjangoFilterConnectionField(RecipeNode)
 
     recipeingredient = Node.Field(RecipeIngredientNode)
     all_recipeingredients = DjangoFilterConnectionField(RecipeIngredientNode)
+
+    @staticmethod
+    @sync_to_async
+    def resolve_raw_recipe(self, info):
+        return Recipe.objects.first()
diff --git a/examples/cookbook/cookbook/urls.py b/examples/cookbook/cookbook/urls.py
index e9e69cd25..541cd2df7 100644
--- a/examples/cookbook/cookbook/urls.py
+++ b/examples/cookbook/cookbook/urls.py
@@ -1,9 +1,9 @@
 from django.urls import re_path
 from django.contrib import admin
-
-from graphene_django.views import GraphQLView
+from django.views.decorators.csrf import csrf_exempt
+from graphene_django.views import AsyncGraphQLView
 
 urlpatterns = [
     re_path(r"^admin/", admin.site.urls),
-    re_path(r"^graphql$", GraphQLView.as_view(graphiql=True)),
+    re_path(r"^graphql$", csrf_exempt(AsyncGraphQLView.as_view(graphiql=True))),
 ]

From 4d5132d925dbd6448aeca876f93ec68f6740cdf8 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 4 May 2023 15:26:52 +0100
Subject: [PATCH 13/35] Remove tests for now

---
 graphene_django/tests/test_query_async.py | 1772 ---------------------
 1 file changed, 1772 deletions(-)
 delete mode 100644 graphene_django/tests/test_query_async.py

diff --git a/graphene_django/tests/test_query_async.py b/graphene_django/tests/test_query_async.py
deleted file mode 100644
index 44eaeb6de..000000000
--- a/graphene_django/tests/test_query_async.py
+++ /dev/null
@@ -1,1772 +0,0 @@
-import datetime
-import base64
-
-from django.db import models
-from django.db.models import Q
-from django.utils.functional import SimpleLazyObject
-from graphql_relay import to_global_id
-from pytest import raises, mark
-from asgiref.sync import sync_to_async
-
-import graphene
-from graphene.relay import Node
-
-from ..compat import IntegerRangeField, MissingType
-from ..fields import DjangoConnectionField
-from ..types import DjangoObjectType
-from ..utils import DJANGO_FILTER_INSTALLED
-from .models import Article, CNNReporter, Film, FilmDetails, Person, Pet, Reporter
-
-@mark.asyncio
-async def test_should_query_only_fields():
-    with raises(Exception):
-
-        class ReporterType(DjangoObjectType):
-            class Meta:
-                model = Reporter
-                fields = ("articles",)
-
-        schema = graphene.Schema(query=ReporterType)
-        query = """
-            query ReporterQuery {
-              articles
-            }
-        """
-        result = await schema.execute_async(query)
-        assert not result.errors
-
-@mark.asyncio
-async def test_should_query_simplelazy_objects():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            fields = ("id",)
-
-    class Query(graphene.ObjectType):
-        reporter = graphene.Field(ReporterType)
-
-        def resolve_reporter(self, info):
-            return SimpleLazyObject(lambda: Reporter(id=1))
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query {
-          reporter {
-            id
-          }
-        }
-    """
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == {"reporter": {"id": "1"}}
-
-@mark.asyncio
-async def test_should_query_wrapped_simplelazy_objects():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            fields = ("id",)
-
-    class Query(graphene.ObjectType):
-        reporter = graphene.Field(ReporterType)
-
-        def resolve_reporter(self, info):
-            return SimpleLazyObject(lambda: SimpleLazyObject(lambda: Reporter(id=1)))
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query {
-          reporter {
-            id
-          }
-        }
-    """
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == {"reporter": {"id": "1"}}
-
-@mark.asyncio
-async def test_should_query_well():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        reporter = graphene.Field(ReporterType)
-
-        def resolve_reporter(self, info):
-            return Reporter(first_name="ABA", last_name="X")
-
-    query = """
-        query ReporterQuery {
-          reporter {
-            firstName,
-            lastName,
-            email
-          }
-        }
-    """
-    expected = {"reporter": {"firstName": "ABA", "lastName": "X", "email": ""}}
-    schema = graphene.Schema(query=Query)
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-@mark.skipif(IntegerRangeField is MissingType, reason="RangeField should exist")
-async def test_should_query_postgres_fields():
-    from django.contrib.postgres.fields import (
-        IntegerRangeField,
-        ArrayField,
-        JSONField,
-        HStoreField,
-    )
-
-    class Event(models.Model):
-        ages = IntegerRangeField(help_text="The age ranges")
-        data = JSONField(help_text="Data")
-        store = HStoreField()
-        tags = ArrayField(models.CharField(max_length=50))
-
-    class EventType(DjangoObjectType):
-        class Meta:
-            model = Event
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        event = graphene.Field(EventType)
-
-        def resolve_event(self, info):
-            return Event(
-                ages=(0, 10),
-                data={"angry_babies": True},
-                store={"h": "store"},
-                tags=["child", "angry", "babies"],
-            )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query myQuery {
-          event {
-            ages
-            tags
-            data
-            store
-          }
-        }
-    """
-    expected = {
-        "event": {
-            "ages": [0, 10],
-            "tags": ["child", "angry", "babies"],
-            "data": '{"angry_babies": true}',
-            "store": '{"h": "store"}',
-        }
-    }
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_node():
-    class ReporterNode(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-        @classmethod
-        def get_node(cls, info, id):
-            return Reporter(id=2, first_name="Cookie Monster")
-
-        def resolve_articles(self, info, **args):
-            return [Article(headline="Hi!")]
-
-    class ArticleNode(DjangoObjectType):
-        class Meta:
-            model = Article
-            interfaces = (Node,)
-            fields = "__all__"
-
-        @classmethod
-        def get_node(cls, info, id):
-            return Article(
-                id=1, headline="Article node", pub_date=datetime.date(2002, 3, 11)
-            )
-
-    class Query(graphene.ObjectType):
-        node = Node.Field()
-        reporter = graphene.Field(ReporterNode)
-        article = graphene.Field(ArticleNode)
-
-        def resolve_reporter(self, info):
-            return Reporter(id=1, first_name="ABA", last_name="X")
-
-    query = """
-        query ReporterQuery {
-          reporter {
-            id,
-            firstName,
-            articles {
-              edges {
-                node {
-                  headline
-                }
-              }
-            }
-            lastName,
-            email
-          }
-          myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") {
-            id
-            ... on ReporterNode {
-                firstName
-            }
-            ... on ArticleNode {
-                headline
-                pubDate
-            }
-          }
-        }
-    """
-    expected = {
-        "reporter": {
-            "id": "UmVwb3J0ZXJOb2RlOjE=",
-            "firstName": "ABA",
-            "lastName": "X",
-            "email": "",
-            "articles": {"edges": [{"node": {"headline": "Hi!"}}]},
-        },
-        "myArticle": {
-            "id": "QXJ0aWNsZU5vZGU6MQ==",
-            "headline": "Article node",
-            "pubDate": "2002-03-11",
-        },
-    }
-    schema = graphene.Schema(query=Query)
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_query_onetoone_fields():
-    film = Film.objects.create(id=1)
-    film_details = FilmDetails.objects.create(id=1, film=film)
-
-    class FilmNode(DjangoObjectType):
-        class Meta:
-            model = Film
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class FilmDetailsNode(DjangoObjectType):
-        class Meta:
-            model = FilmDetails
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        film = graphene.Field(FilmNode)
-        film_details = graphene.Field(FilmDetailsNode)
-
-        def resolve_film(root, info):
-            return film
-
-        def resolve_film_details(root, info):
-            return film_details
-
-    query = """
-        query FilmQuery {
-          filmDetails {
-            id
-            film {
-              id
-            }
-          }
-          film {
-            id
-            details {
-              id
-            }
-          }
-        }
-    """
-    expected = {
-        "filmDetails": {
-            "id": "RmlsbURldGFpbHNOb2RlOjE=",
-            "film": {"id": "RmlsbU5vZGU6MQ=="},
-        },
-        "film": {
-            "id": "RmlsbU5vZGU6MQ==",
-            "details": {"id": "RmlsbURldGFpbHNOb2RlOjE="},
-        },
-    }
-    schema = graphene.Schema(query=Query)
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_query_connectionfields():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = ("articles",)
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-        def resolve_all_reporters(self, info, **args):
-            return [Reporter(id=1)]
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterConnectionQuery {
-          allReporters {
-            pageInfo {
-              hasNextPage
-            }
-            edges {
-              node {
-                id
-              }
-            }
-          }
-        }
-    """
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == {
-        "allReporters": {
-            "pageInfo": {"hasNextPage": False},
-            "edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}],
-        }
-    }
-
-@mark.asyncio
-async def test_should_keep_annotations():
-    from django.db.models import Count, Avg
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = ("articles",)
-
-    class ArticleType(DjangoObjectType):
-        class Meta:
-            model = Article
-            interfaces = (Node,)
-            fields = "__all__"
-            filter_fields = ("lang",)
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-        all_articles = DjangoConnectionField(ArticleType)
-
-        @staticmethod
-        @sync_to_async
-        def resolve_all_reporters(self, info, **args):
-            return Reporter.objects.annotate(articles_c=Count("articles")).order_by(
-                "articles_c"
-            )
-
-        @staticmethod
-        @sync_to_async
-        def resolve_all_articles(self, info, **args):
-            return Article.objects.annotate(import_avg=Avg("importance")).order_by(
-                "import_avg"
-            )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterConnectionQuery {
-          allReporters {
-            pageInfo {
-              hasNextPage
-            }
-            edges {
-              node {
-                id
-              }
-            }
-          }
-          allArticles {
-            pageInfo {
-              hasNextPage
-            }
-            edges {
-              node {
-                id
-              }
-            }
-          }
-        }
-    """
-    result = await schema.execute_async(query)
-    assert not result.errors
-
-
-@mark.skipif(
-    not DJANGO_FILTER_INSTALLED, reason="django-filter should be installed")
-@mark.asyncio
-async def test_should_query_node_filtering():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class ArticleType(DjangoObjectType):
-        class Meta:
-            model = Article
-            interfaces = (Node,)
-            fields = "__all__"
-            filter_fields = ("lang",)
-            convert_choices_to_enum = False
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-    Article.objects.create(
-        headline="Article Node 1",
-        pub_date=datetime.date.today(),
-        pub_date_time=datetime.datetime.now(),
-        reporter=r,
-        editor=r,
-        lang="es",
-    )
-    Article.objects.create(
-        headline="Article Node 2",
-        pub_date=datetime.date.today(),
-        pub_date_time=datetime.datetime.now(),
-        reporter=r,
-        editor=r,
-        lang="en",
-    )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query NodeFilteringQuery {
-            allReporters {
-                edges {
-                    node {
-                        id
-                        articles(lang: "es") {
-                            edges {
-                                node {
-                                    id
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {
-        "allReporters": {
-            "edges": [
-                {
-                    "node": {
-                        "id": "UmVwb3J0ZXJUeXBlOjE=",
-                        "articles": {
-                            "edges": [{"node": {"id": "QXJ0aWNsZVR5cGU6MQ=="}}]
-                        },
-                    }
-                }
-            ]
-        }
-    }
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-
-@mark.skipif(
-    not DJANGO_FILTER_INSTALLED, reason="django-filter should be installed")
-@mark.asyncio
-async def test_should_query_node_filtering_with_distinct_queryset():
-    class FilmType(DjangoObjectType):
-        class Meta:
-            model = Film
-            interfaces = (Node,)
-            fields = "__all__"
-            filter_fields = ("genre",)
-
-    class Query(graphene.ObjectType):
-        films = DjangoConnectionField(FilmType)
-
-        # def resolve_all_reporters_with_berlin_films(self, args, context, info):
-        #    return Reporter.objects.filter(Q(films__film__location__contains="Berlin") | Q(a_choice=1))
-
-        @sync_to_async
-        def resolve_films(self, info, **args):
-            return Film.objects.filter(
-                Q(details__location__contains="Berlin") | Q(genre__in=["ot"])
-            ).distinct()
-
-    f = Film.objects.create()
-    fd = FilmDetails.objects.create(location="Berlin", film=f)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query NodeFilteringQuery {
-            films {
-                edges {
-                    node {
-                        genre
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"films": {"edges": [{"node": {"genre": "OT"}}]}}
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-
-@mark.skipif(not DJANGO_FILTER_INSTALLED, reason="django-filter should be installed")
-@mark.asyncio
-async def test_should_query_node_multiple_filtering():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class ArticleType(DjangoObjectType):
-        class Meta:
-            model = Article
-            interfaces = (Node,)
-            fields = "__all__"
-            filter_fields = ("lang", "headline")
-            convert_choices_to_enum = False
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-    Article.objects.create(
-        headline="Article Node 1",
-        pub_date=datetime.date.today(),
-        pub_date_time=datetime.datetime.now(),
-        reporter=r,
-        editor=r,
-        lang="es",
-    )
-    Article.objects.create(
-        headline="Article Node 2",
-        pub_date=datetime.date.today(),
-        pub_date_time=datetime.datetime.now(),
-        reporter=r,
-        editor=r,
-        lang="es",
-    )
-    Article.objects.create(
-        headline="Article Node 3",
-        pub_date=datetime.date.today(),
-        pub_date_time=datetime.datetime.now(),
-        reporter=r,
-        editor=r,
-        lang="en",
-    )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query NodeFilteringQuery {
-            allReporters {
-                edges {
-                    node {
-                        id
-                        articles(lang: "es", headline: "Article Node 1") {
-                            edges {
-                                node {
-                                    id
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {
-        "allReporters": {
-            "edges": [
-                {
-                    "node": {
-                        "id": "UmVwb3J0ZXJUeXBlOjE=",
-                        "articles": {
-                            "edges": [{"node": {"id": "QXJ0aWNsZVR5cGU6MQ=="}}]
-                        },
-                    }
-                }
-            ]
-        }
-    }
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_enforce_first_or_last(graphene_settings):
-    graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = True
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query NodeFilteringQuery {
-            allReporters {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"allReporters": None}
-
-    result = await schema.execute_async(query)
-    assert len(result.errors) == 1
-    assert str(result.errors[0]).startswith(
-        "You must provide a `first` or `last` value to properly "
-        "paginate the `allReporters` connection.\n"
-    )
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_error_if_first_is_greater_than_max(graphene_settings):
-    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    assert Query.all_reporters.max_limit == 100
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query NodeFilteringQuery {
-            allReporters(first: 101) {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"allReporters": None}
-
-    result = await schema.execute_async(query)
-    assert len(result.errors) == 1
-    assert str(result.errors[0]).startswith(
-        "Requesting 101 records on the `allReporters` connection "
-        "exceeds the `first` limit of 100 records.\n"
-    )
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_error_if_last_is_greater_than_max(graphene_settings):
-    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    assert Query.all_reporters.max_limit == 100
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query NodeFilteringQuery {
-            allReporters(last: 101) {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"allReporters": None}
-
-    result = await schema.execute_async(query)
-    assert len(result.errors) == 1
-    assert str(result.errors[0]).startswith(
-        "Requesting 101 records on the `allReporters` connection "
-        "exceeds the `last` limit of 100 records.\n"
-    )
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_query_promise_connectionfields():
-    from promise import Promise
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-        def resolve_all_reporters(self, info, **args):
-            return Promise.resolve([Reporter(id=1)]).get()
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery {
-            allReporters(first: 1) {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_query_connectionfields_with_last():
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-        def resolve_all_reporters(self, info, **args):
-            return Reporter.objects.all()
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterLastQuery {
-            allReporters(last: 1) {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_query_connectionfields_with_manager():
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="NotDoe", email="johndoe@example.com", a_choice=1
-    )
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType, on="doe_objects")
-
-        def resolve_all_reporters(self, info, **args):
-            return Reporter.objects.all()
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterLastQuery {
-            allReporters(first: 1) {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_query_dataloader_fields():
-    from promise import Promise
-    from promise.dataloader import DataLoader
-
-    def article_batch_load_fn(keys):
-        queryset = Article.objects.filter(reporter_id__in=keys)
-        return Promise.resolve(
-            [
-                [article for article in queryset if article.reporter_id == id]
-                for id in keys
-            ]
-        )
-
-    article_loader = DataLoader(article_batch_load_fn)
-
-    class ArticleType(DjangoObjectType):
-        class Meta:
-            model = Article
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            use_connection = True
-            fields = "__all__"
-
-        articles = DjangoConnectionField(ArticleType)
-
-        def resolve_articles(self, info, **args):
-            return article_loader.load(self.id).get()
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    r = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-
-    Article.objects.create(
-        headline="Article Node 1",
-        pub_date=datetime.date.today(),
-        pub_date_time=datetime.datetime.now(),
-        reporter=r,
-        editor=r,
-        lang="es",
-    )
-    Article.objects.create(
-        headline="Article Node 2",
-        pub_date=datetime.date.today(),
-        pub_date_time=datetime.datetime.now(),
-        reporter=r,
-        editor=r,
-        lang="en",
-    )
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery {
-            allReporters(first: 1) {
-                edges {
-                    node {
-                        id
-                        articles(first: 2) {
-                            edges {
-                                node {
-                                    headline
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {
-        "allReporters": {
-            "edges": [
-                {
-                    "node": {
-                        "id": "UmVwb3J0ZXJUeXBlOjE=",
-                        "articles": {
-                            "edges": [
-                                {"node": {"headline": "Article Node 1"}},
-                                {"node": {"headline": "Article Node 2"}},
-                            ]
-                        },
-                    }
-                }
-            ]
-        }
-    }
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_handle_inherited_choices():
-    class BaseModel(models.Model):
-        choice_field = models.IntegerField(choices=((0, "zero"), (1, "one")))
-
-    class ChildModel(BaseModel):
-        class Meta:
-            proxy = True
-
-    class BaseType(DjangoObjectType):
-        class Meta:
-            model = BaseModel
-            fields = "__all__"
-
-    class ChildType(DjangoObjectType):
-        class Meta:
-            model = ChildModel
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        base = graphene.Field(BaseType)
-        child = graphene.Field(ChildType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query {
-          child {
-            choiceField
-          }
-        }
-    """
-    result = await schema.execute_async(query)
-    assert not result.errors
-
-@mark.asyncio
-async def test_proxy_model_support():
-    """
-    This test asserts that we can query for all Reporters and proxied Reporters.
-    """
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            use_connection = True
-            fields = "__all__"
-
-    class CNNReporterType(DjangoObjectType):
-        class Meta:
-            model = CNNReporter
-            interfaces = (Node,)
-            use_connection = True
-            fields = "__all__"
-
-    reporter = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-
-    cnn_reporter = CNNReporter.objects.create(
-        first_name="Some",
-        last_name="Guy",
-        email="someguy@cnn.com",
-        a_choice=1,
-        reporter_type=2,  # set this guy to be CNN
-    )
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-        cnn_reporters = DjangoConnectionField(CNNReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ProxyModelQuery {
-            allReporters {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-            cnnReporters {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"id": to_global_id("ReporterType", reporter.id)}},
-                {"node": {"id": to_global_id("ReporterType", cnn_reporter.id)}},
-            ]
-        },
-        "cnnReporters": {
-            "edges": [
-                {"node": {"id": to_global_id("CNNReporterType", cnn_reporter.id)}}
-            ]
-        },
-    }
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_resolve_get_queryset_connectionfields():
-    reporter_1 = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-    reporter_2 = CNNReporter.objects.create(
-        first_name="Some",
-        last_name="Guy",
-        email="someguy@cnn.com",
-        a_choice=1,
-        reporter_type=2,  # set this guy to be CNN
-    )
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-        @classmethod
-        def get_queryset(cls, queryset, info):
-            return queryset.filter(reporter_type=2)
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery {
-            allReporters(first: 1) {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjI="}}]}}
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert result.data == expected
-
-@mark.asyncio
-async def test_connection_should_limit_after_to_list_length():
-    reporter_1 = Reporter.objects.create(
-        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
-    )
-    reporter_2 = Reporter.objects.create(
-        first_name="Some", last_name="Guy", email="someguy@cnn.com", a_choice=1
-    )
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery ($after: String) {
-            allReporters(first: 1 after: $after) {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    after = base64.b64encode(b"arrayconnection:10").decode()
-    result = await schema.execute_async(query, variable_values=dict(after=after))
-    expected = {"allReporters": {"edges": []}}
-    assert not result.errors
-    assert result.data == expected
-
-
-REPORTERS = [
-    dict(
-        first_name=f"First {i}",
-        last_name=f"Last {i}",
-        email=f"johndoe+{i}@example.com",
-        a_choice=1,
-    )
-    for i in range(6)
-]
-
-@mark.asyncio
-async def test_should_return_max_limit(graphene_settings):
-    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 4
-    reporters = [Reporter(**kwargs) for kwargs in REPORTERS]
-    Reporter.objects.bulk_create(reporters)
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query AllReporters {
-            allReporters {
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    assert len(result.data["allReporters"]["edges"]) == 4
-
-@mark.asyncio
-async def test_should_have_next_page(graphene_settings):
-    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 4
-    reporters = [Reporter(**kwargs) for kwargs in REPORTERS]
-    await Reporter.objects.abulk_create(reporters)
-    db_reporters = await sync_to_async(Reporter.objects.all)()
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query AllReporters($first: Int, $after: String) {
-            allReporters(first: $first, after: $after) {
-                pageInfo {
-                    hasNextPage
-                    endCursor
-                }
-                edges {
-                    node {
-                        id
-                    }
-                }
-            }
-        }
-    """
-
-    result = await schema.execute_async(query, variable_values={})
-    assert not result.errors
-    assert len(result.data["allReporters"]["edges"]) == 4
-    assert result.data["allReporters"]["pageInfo"]["hasNextPage"]
-
-    last_result = result.data["allReporters"]["pageInfo"]["endCursor"]
-    result2 = await schema.execute_async(query, variable_values=dict(first=4, after=last_result))
-    assert not result2.errors
-    assert len(result2.data["allReporters"]["edges"]) == 2
-    assert not result2.data["allReporters"]["pageInfo"]["hasNextPage"]
-    gql_reporters = (
-        result.data["allReporters"]["edges"] + result2.data["allReporters"]["edges"]
-    )
-
-    def get_test():    
-        assert {to_global_id("ReporterType", reporter.id) for reporter in db_reporters} == {
-            gql_reporter["node"]["id"] for gql_reporter in gql_reporters
-        }
-    await sync_to_async(get_test)()
-
-@mark.parametrize("max_limit", [100, 4])
-class TestBackwardPagination:
-    def setup_schema(self, graphene_settings, max_limit):
-        graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
-        reporters = [Reporter(**kwargs) for kwargs in REPORTERS]
-        Reporter.objects.bulk_create(reporters)
-
-        class ReporterType(DjangoObjectType):
-            class Meta:
-                model = Reporter
-                interfaces = (Node,)
-                fields = "__all__"
-
-        class Query(graphene.ObjectType):
-            all_reporters = DjangoConnectionField(ReporterType)
-
-        schema = graphene.Schema(query=Query)
-        return schema
-    @mark.asyncio
-    async def test_query_last(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_last = """
-            query {
-                allReporters(last: 3) {
-                    edges {
-                        node {
-                            firstName
-                        }
-                    }
-                }
-            }
-        """
-
-        result = await schema.execute_async(query_last)
-        assert not result.errors
-        assert len(result.data["allReporters"]["edges"]) == 3
-        assert [
-            e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
-        ] == ["First 3", "First 4", "First 5"]
-    @mark.asyncio
-    async def test_query_first_and_last(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_first_and_last = """
-            query {
-                allReporters(first: 4, last: 3) {
-                    edges {
-                        node {
-                            firstName
-                        }
-                    }
-                }
-            }
-        """
-
-        result = await schema.execute_async(query_first_and_last)
-        assert not result.errors
-        assert len(result.data["allReporters"]["edges"]) == 3
-        assert [
-            e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
-        ] == ["First 1", "First 2", "First 3"]
-    @mark.asyncio
-    async def test_query_first_last_and_after(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_first_last_and_after = """
-            query queryAfter($after: String) {
-                allReporters(first: 4, last: 3, after: $after) {
-                    edges {
-                        node {
-                            firstName
-                        }
-                    }
-                }
-            }
-        """
-
-        after = base64.b64encode(b"arrayconnection:0").decode()
-        result = await schema.execute_async(
-            query_first_last_and_after,
-            variable_values=dict(after=after),
-        )
-        assert not result.errors
-        assert len(result.data["allReporters"]["edges"]) == 3
-        assert [
-            e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
-        ] == ["First 2", "First 3", "First 4"]
-    @mark.asyncio
-    async def test_query_last_and_before(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_first_last_and_after = """
-            query queryAfter($before: String) {
-                allReporters(last: 1, before: $before) {
-                    edges {
-                        node {
-                            firstName
-                        }
-                    }
-                }
-            }
-        """
-
-        result = await schema.execute_async(
-            query_first_last_and_after,
-        )
-        assert not result.errors
-        assert len(result.data["allReporters"]["edges"]) == 1
-        assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 5"
-
-        before = base64.b64encode(b"arrayconnection:5").decode()
-        result = await schema.execute_async(
-            query_first_last_and_after,
-            variable_values=dict(before=before),
-        )
-        assert not result.errors
-        assert len(result.data["allReporters"]["edges"]) == 1
-        assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 4"
-
-@mark.asyncio
-async def test_should_preserve_prefetch_related(django_assert_num_queries):
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (graphene.relay.Node,)
-            fields = "__all__"
-
-    class FilmType(DjangoObjectType):
-        reporters = DjangoConnectionField(ReporterType)
-
-        class Meta:
-            model = Film
-            interfaces = (graphene.relay.Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        films = DjangoConnectionField(FilmType)
-
-        def resolve_films(root, info, **kwargs):
-            qs = Film.objects.prefetch_related("reporters")
-            return qs
-
-    r1 = Reporter.objects.create(first_name="Dave", last_name="Smith")
-    r2 = Reporter.objects.create(first_name="Jane", last_name="Doe")
-
-    f1 = Film.objects.create()
-    f1.reporters.set([r1, r2])
-    f2 = Film.objects.create()
-    f2.reporters.set([r2])
-
-    query = """
-        query {
-            films {
-                edges {
-                    node {
-                        reporters {
-                            edges {
-                                node {
-                                    firstName
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    """
-    schema = graphene.Schema(query=Query)
-
-    with django_assert_num_queries(3) as captured:
-        result = await schema.execute_async(query)
-        assert not result.errors
-
-@mark.asyncio
-async def test_should_preserve_annotations():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (graphene.relay.Node,)
-            fields = "__all__"
-
-    class FilmType(DjangoObjectType):
-        reporters = DjangoConnectionField(ReporterType)
-        reporters_count = graphene.Int()
-
-        class Meta:
-            model = Film
-            interfaces = (graphene.relay.Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        films = DjangoConnectionField(FilmType)
-
-        def resolve_films(root, info, **kwargs):
-            qs = Film.objects.prefetch_related("reporters")
-            return qs.annotate(reporters_count=models.Count("reporters"))
-
-    r1 = Reporter.objects.create(first_name="Dave", last_name="Smith")
-    r2 = Reporter.objects.create(first_name="Jane", last_name="Doe")
-
-    f1 = Film.objects.create()
-    f1.reporters.set([r1, r2])
-    f2 = Film.objects.create()
-    f2.reporters.set([r2])
-
-    query = """
-        query {
-            films {
-                edges {
-                    node {
-                        reportersCount
-                    }
-                }
-            }
-        }
-    """
-    schema = graphene.Schema(query=Query)
-    result = await schema.execute_async(query)
-    assert not result.errors, str(result)
-
-    expected = {
-        "films": {
-            "edges": [{"node": {"reportersCount": 2}}, {"node": {"reportersCount": 1}}]
-        }
-    }
-    assert result.data == expected, str(result.data)
-    assert not result.errors
-
-@mark.asyncio
-async def test_connection_should_enable_offset_filtering():
-    Reporter.objects.create(first_name="John", last_name="Doe")
-    Reporter.objects.create(first_name="Some", last_name="Guy")
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query {
-            allReporters(first: 1, offset: 1) {
-                edges {
-                    node {
-                        firstName
-                        lastName
-                    }
-                }
-            }
-        }
-    """
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Some", "lastName": "Guy"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-@mark.asyncio
-async def test_connection_should_enable_offset_filtering_higher_than_max_limit(
-    graphene_settings,
-):
-    graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 2
-    Reporter.objects.create(first_name="John", last_name="Doe")
-    Reporter.objects.create(first_name="Some", last_name="Guy")
-    Reporter.objects.create(first_name="Jane", last_name="Roe")
-    Reporter.objects.create(first_name="Some", last_name="Lady")
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query {
-            allReporters(first: 1, offset: 3) {
-                edges {
-                    node {
-                        firstName
-                        lastName
-                    }
-                }
-            }
-        }
-    """
-
-    result = await schema.execute_async(query)
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-@mark.asyncio
-async def test_connection_should_forbid_offset_filtering_with_before():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery ($before: String) {
-            allReporters(first: 1, before: $before, offset: 1) {
-                edges {
-                    node {
-                        firstName
-                        lastName
-                    }
-                }
-            }
-        }
-    """
-    before = base64.b64encode(b"arrayconnection:2").decode()
-    result = await schema.execute_async(query, variable_values=dict(before=before))
-    expected_error = "You can't provide a `before` value at the same time as an `offset` value to properly paginate the `allReporters` connection."
-    assert len(result.errors) == 1
-    assert result.errors[0].message == expected_error
-
-@mark.asyncio
-async def test_connection_should_allow_offset_filtering_with_after():
-    Reporter.objects.create(first_name="John", last_name="Doe")
-    Reporter.objects.create(first_name="Some", last_name="Guy")
-    Reporter.objects.create(first_name="Jane", last_name="Roe")
-    Reporter.objects.create(first_name="Some", last_name="Lady")
-
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery ($after: String) {
-            allReporters(first: 1, after: $after, offset: 1) {
-                edges {
-                    node {
-                        firstName
-                        lastName
-                    }
-                }
-            }
-        }
-    """
-
-    after = base64.b64encode(b"arrayconnection:0").decode()
-    result = await schema.execute_async(query, variable_values=dict(after=after))
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-@mark.asyncio
-async def test_connection_should_succeed_if_last_higher_than_number_of_objects():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery ($last: Int) {
-            allReporters(last: $last) {
-                edges {
-                    node {
-                        firstName
-                        lastName
-                    }
-                }
-            }
-        }
-    """
-
-    result = await schema.execute_async(query, variable_values=dict(last=2))
-    assert not result.errors
-    expected = {"allReporters": {"edges": []}}
-    assert result.data == expected
-
-    Reporter.objects.create(first_name="John", last_name="Doe")
-    Reporter.objects.create(first_name="Some", last_name="Guy")
-    Reporter.objects.create(first_name="Jane", last_name="Roe")
-    Reporter.objects.create(first_name="Some", last_name="Lady")
-
-    result = await schema.execute_async(query, variable_values=dict(last=2))
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-    result = await schema.execute_async(query, variable_values=dict(last=4))
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "John", "lastName": "Doe"}},
-                {"node": {"firstName": "Some", "lastName": "Guy"}},
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-    result = await schema.execute_async(query, variable_values=dict(last=20))
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "John", "lastName": "Doe"}},
-                {"node": {"firstName": "Some", "lastName": "Guy"}},
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-@mark.asyncio
-async def test_should_query_nullable_foreign_key():
-    class PetType(DjangoObjectType):
-        class Meta:
-            model = Pet
-
-    class PersonType(DjangoObjectType):
-        class Meta:
-            model = Person
-
-    class Query(graphene.ObjectType):
-        pet = graphene.Field(PetType, name=graphene.String(required=True))
-        person = graphene.Field(PersonType, name=graphene.String(required=True))
-        
-        @staticmethod
-        @sync_to_async
-        def resolve_pet(self, info, name):
-            return Pet.objects.filter(name=name).first()
-
-        @staticmethod
-        @sync_to_async
-        def resolve_person(self, info, name):
-            return Person.objects.filter(name=name).first()
-
-    schema = graphene.Schema(query=Query)
-
-    person = await Person.objects.acreate(name="Jane")
-    pets = [
-        await Pet.objects.acreate(name="Stray dog", age=1),
-        await Pet.objects.acreate(name="Jane's dog", owner=person, age=1),
-    ]
-
-    query_pet = """
-        query getPet($name: String!) {
-            pet(name: $name) {
-                owner {
-                    name
-                }
-            }
-        }
-    """
-    result = await schema.execute_async(query_pet, variables={"name": "Stray dog"})
-    assert not result.errors
-    assert result.data["pet"] == {
-        "owner": None,
-    }
-
-    result = await schema.execute_async(query_pet, variables={"name": "Jane's dog"})
-    assert not result.errors
-    assert result.data["pet"] == {
-        "owner": {"name": "Jane"},
-    }
-
-    query_owner = """
-        query getOwner($name: String!) {
-            person(name: $name) {
-                pets {
-                    name
-                }
-            }
-        }
-    """
-    result = await schema.execute_async(query_owner, variables={"name": "Jane"})
-    assert not result.errors
-    assert result.data["person"] == {
-        "pets": [{"name": "Jane's dog"}],
-    }

From 4e5862f8fb0c8fb716a129abeb2cc6cdf278777d Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 4 May 2023 15:27:47 +0100
Subject: [PATCH 14/35] Add logging of errors in execution

---
 graphene_django/views.py | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/graphene_django/views.py b/graphene_django/views.py
index a98a8c50d..f195efea2 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -1,6 +1,7 @@
 import inspect
 import json
 import re
+import traceback
 
 from asyncio import gather, coroutines
 
@@ -519,7 +520,9 @@ async def dispatch(self, request, *args, **kwargs):
                 )
 
             if self.batch:
-                responses = await gather(*[self.get_response(request, entry) for entry in data])
+                responses = await gather(
+                    *[self.get_response(request, entry) for entry in data]
+                )
                 result = "[{}]".format(
                     ",".join([response[0] for response in responses])
                 )
@@ -529,7 +532,9 @@ async def dispatch(self, request, *args, **kwargs):
                     or 200
                 )
             else:
-                result, status_code = await self.get_response(request, data, show_graphiql)
+                result, status_code = await self.get_response(
+                    request, data, show_graphiql
+                )
 
             return HttpResponse(
                 status=status_code, content=result, content_type="application/json"
@@ -558,6 +563,9 @@ async def get_response(self, request, data, show_graphiql=False):
             response = {}
 
             if execution_result.errors:
+                for e in execution_result.errors:
+                    print(e)
+                    traceback.print_tb(e.__traceback__)
                 set_rollback()
                 response["errors"] = [
                     self.format_error(e) for e in execution_result.errors

From c10753d4b1d08b9f0f835e44e936567a9675aa2d Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Thu, 4 May 2023 15:51:25 +0100
Subject: [PATCH 15/35] most recent changes

---
 .pre-commit-config.yaml      |  2 +-
 graphene_django/converter.py | 22 ++++++++++++++++++----
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9214d35eb..adb54c7a1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
 default_language_version:
-  python: python3.11
+  python: python3.10
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v4.4.0
diff --git a/graphene_django/converter.py b/graphene_django/converter.py
index 90c2128df..760973f09 100644
--- a/graphene_django/converter.py
+++ b/graphene_django/converter.py
@@ -1,6 +1,7 @@
 from collections import OrderedDict
 from functools import singledispatch, wraps
 from asyncio import get_running_loop
+from asgiref.sync import sync_to_async
 
 from django.db import models
 from django.utils.encoding import force_str
@@ -265,17 +266,17 @@ def dynamic_type():
         _type = registry.get_type_for_model(model)
         if not _type:
             return
-        
+
         class CustomField(Field):
             def wrap_resolve(self, parent_resolver):
                 resolver = super().wrap_resolve(parent_resolver)
 
-                try: 
+                try:
                     get_running_loop()
                 except RuntimeError:
                     pass
                 else:
-                    resolver=sync_to_async(resolver)
+                    resolver = sync_to_async(resolver)
 
                 return resolver
 
@@ -334,7 +335,20 @@ def dynamic_type():
         if not _type:
             return
 
-        return Field(
+        class CustomField(Field):
+            def wrap_resolve(self, parent_resolver):
+                resolver = super().wrap_resolve(parent_resolver)
+
+                try:
+                    get_running_loop()
+                except RuntimeError:
+                    pass
+                else:
+                    resolver = sync_to_async(resolver)
+
+                return resolver
+
+        return CustomField(
             _type,
             description=get_django_field_description(field),
             required=not field.null,

From e9d5e88ea25b68c57c4a07a576010ea60f2dbfbe Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 5 May 2023 11:18:21 +0100
Subject: [PATCH 16/35] Handle the default django list field and test the async
 execution of the fields

---
 graphene_django/fields.py                  |  52 ++++++--
 graphene_django/tests/async_test_helper.py |   6 +
 graphene_django/tests/test_fields.py       | 139 +++++++++++++++++++++
 3 files changed, 184 insertions(+), 13 deletions(-)
 create mode 100644 graphene_django/tests/async_test_helper.py

diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 6e1b0b1de..99e84c76b 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -53,7 +53,28 @@ def get_manager(self):
     def list_resolver(
         django_object_type, resolver, default_manager, root, info, **args
     ):
-        queryset = maybe_queryset(resolver(root, info, **args))
+        iterable = resolver(root, info, **args)
+
+        if info.is_awaitable(iterable):
+
+            async def resolve_list_async(iterable):
+                queryset = maybe_queryset(await iterable)
+                if queryset is None:
+                    queryset = maybe_queryset(default_manager)
+
+                if isinstance(queryset, QuerySet):
+                    # Pass queryset to the DjangoObjectType get_queryset method
+                    queryset = maybe_queryset(
+                        await sync_to_async(django_object_type.get_queryset)(
+                            queryset, info
+                        )
+                    )
+
+                return await sync_to_async(list)(queryset)
+
+            return resolve_list_async(iterable)
+
+        queryset = maybe_queryset(iterable)
         if queryset is None:
             queryset = maybe_queryset(default_manager)
 
@@ -61,12 +82,12 @@ def list_resolver(
             # Pass queryset to the DjangoObjectType get_queryset method
             queryset = maybe_queryset(django_object_type.get_queryset(queryset, info))
 
-        try: 
+        try:
             get_running_loop()
         except RuntimeError:
-                pass
+            pass
         else:
-            return queryset.aiterator()
+            return sync_to_async(list)(queryset)
 
         return queryset
 
@@ -238,34 +259,39 @@ def connection_resolver(
         # or a resolve_foo (does not accept queryset)
 
         iterable = resolver(root, info, **args)
-        
+
         if info.is_awaitable(iterable):
+
             async def resolve_connection_async(iterable):
                 iterable = await iterable
                 if iterable is None:
                     iterable = default_manager
                 ## This could also be async
                 iterable = queryset_resolver(connection, iterable, info, args)
-                
+
                 if info.is_awaitable(iterable):
                     iterable = await iterable
-                
-                return await sync_to_async(cls.resolve_connection)(connection, args, iterable, max_limit=max_limit)
+
+                return await sync_to_async(cls.resolve_connection)(
+                    connection, args, iterable, max_limit=max_limit
+                )
+
             return resolve_connection_async(iterable)
-        
+
         if iterable is None:
             iterable = default_manager
         # thus the iterable gets refiltered by resolve_queryset
         # but iterable might be promise
         iterable = queryset_resolver(connection, iterable, info, args)
 
-        try: 
+        try:
             get_running_loop()
         except RuntimeError:
-                pass
+            pass
         else:
-            return sync_to_async(cls.resolve_connection)(connection, args, iterable, max_limit=max_limit)
-
+            return sync_to_async(cls.resolve_connection)(
+                connection, args, iterable, max_limit=max_limit
+            )
 
         return cls.resolve_connection(connection, args, iterable, max_limit=max_limit)
 
diff --git a/graphene_django/tests/async_test_helper.py b/graphene_django/tests/async_test_helper.py
new file mode 100644
index 000000000..0487f8918
--- /dev/null
+++ b/graphene_django/tests/async_test_helper.py
@@ -0,0 +1,6 @@
+from asgiref.sync import async_to_sync
+
+
+def assert_async_result_equal(schema, query, result):
+    async_result = async_to_sync(schema.execute_async)(query)
+    assert async_result == result
diff --git a/graphene_django/tests/test_fields.py b/graphene_django/tests/test_fields.py
index 8c7b78d36..2a5055398 100644
--- a/graphene_django/tests/test_fields.py
+++ b/graphene_django/tests/test_fields.py
@@ -1,6 +1,7 @@
 import datetime
 import re
 from django.db.models import Count, Prefetch
+from asgiref.sync import sync_to_async, async_to_sync
 
 import pytest
 
@@ -14,6 +15,7 @@
     FilmDetails as FilmDetailsModel,
     Reporter as ReporterModel,
 )
+from .async_test_helper import assert_async_result_equal
 
 
 class TestDjangoListField:
@@ -75,6 +77,7 @@ class Query(ObjectType):
 
         result = schema.execute(query)
 
+        assert_async_result_equal(schema, query, result)
         assert not result.errors
         assert result.data == {
             "reporters": [{"firstName": "Tara"}, {"firstName": "Debra"}]
@@ -102,6 +105,7 @@ class Query(ObjectType):
         result = schema.execute(query)
         assert not result.errors
         assert result.data == {"reporters": []}
+        assert_async_result_equal(schema, query, result)
 
         ReporterModel.objects.create(first_name="Tara", last_name="West")
         ReporterModel.objects.create(first_name="Debra", last_name="Payne")
@@ -112,6 +116,7 @@ class Query(ObjectType):
         assert result.data == {
             "reporters": [{"firstName": "Tara"}, {"firstName": "Debra"}]
         }
+        assert_async_result_equal(schema, query, result)
 
     def test_override_resolver(self):
         class Reporter(DjangoObjectType):
@@ -139,6 +144,37 @@ def resolve_reporters(_, info):
         ReporterModel.objects.create(first_name="Debra", last_name="Payne")
 
         result = schema.execute(query)
+        assert not result.errors
+        assert result.data == {"reporters": [{"firstName": "Tara"}]}
+
+    def test_override_resolver_async_execution(self):
+        class Reporter(DjangoObjectType):
+            class Meta:
+                model = ReporterModel
+                fields = ("first_name",)
+
+        class Query(ObjectType):
+            reporters = DjangoListField(Reporter)
+
+            @staticmethod
+            @sync_to_async
+            def resolve_reporters(_, info):
+                return ReporterModel.objects.filter(first_name="Tara")
+
+        schema = Schema(query=Query)
+
+        query = """
+            query {
+                reporters {
+                    firstName
+                }
+            }
+        """
+
+        ReporterModel.objects.create(first_name="Tara", last_name="West")
+        ReporterModel.objects.create(first_name="Debra", last_name="Payne")
+
+        result = async_to_sync(schema.execute_async)(query)
 
         assert not result.errors
         assert result.data == {"reporters": [{"firstName": "Tara"}]}
@@ -203,6 +239,7 @@ class Query(ObjectType):
                 {"firstName": "Debra", "articles": []},
             ]
         }
+        assert_async_result_equal(schema, query, result)
 
     def test_override_resolver_nested_list_field(self):
         class Article(DjangoObjectType):
@@ -261,6 +298,7 @@ class Query(ObjectType):
                 {"firstName": "Debra", "articles": []},
             ]
         }
+        assert_async_result_equal(schema, query, result)
 
     def test_get_queryset_filter(self):
         class Reporter(DjangoObjectType):
@@ -306,6 +344,7 @@ def resolve_reporters(_, info):
 
         assert not result.errors
         assert result.data == {"reporters": [{"firstName": "Tara"}]}
+        assert_async_result_equal(schema, query, result)
 
     def test_resolve_list(self):
         """Resolving a plain list should work (and not call get_queryset)"""
@@ -354,6 +393,55 @@ def resolve_reporters(_, info):
         assert not result.errors
         assert result.data == {"reporters": [{"firstName": "Debra"}]}
 
+    def test_resolve_list_async(self):
+        """Resolving a plain list should work (and not call get_queryset) when running under async"""
+
+        class Reporter(DjangoObjectType):
+            class Meta:
+                model = ReporterModel
+                fields = ("first_name", "articles")
+
+            @classmethod
+            def get_queryset(cls, queryset, info):
+                # Only get reporters with at least 1 article
+                return queryset.annotate(article_count=Count("articles")).filter(
+                    article_count__gt=0
+                )
+
+        class Query(ObjectType):
+            reporters = DjangoListField(Reporter)
+
+            @staticmethod
+            @sync_to_async
+            def resolve_reporters(_, info):
+                return [ReporterModel.objects.get(first_name="Debra")]
+
+        schema = Schema(query=Query)
+
+        query = """
+            query {
+                reporters {
+                    firstName
+                }
+            }
+        """
+
+        r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
+        ReporterModel.objects.create(first_name="Debra", last_name="Payne")
+
+        ArticleModel.objects.create(
+            headline="Amazing news",
+            reporter=r1,
+            pub_date=datetime.date.today(),
+            pub_date_time=datetime.datetime.now(),
+            editor=r1,
+        )
+
+        result = async_to_sync(schema.execute_async)(query)
+
+        assert not result.errors
+        assert result.data == {"reporters": [{"firstName": "Debra"}]}
+
     def test_get_queryset_foreign_key(self):
         class Article(DjangoObjectType):
             class Meta:
@@ -413,6 +501,7 @@ class Query(ObjectType):
                 {"firstName": "Debra", "articles": []},
             ]
         }
+        assert_async_result_equal(schema, query, result)
 
     def test_resolve_list_external_resolver(self):
         """Resolving a plain list from external resolver should work (and not call get_queryset)"""
@@ -461,6 +550,54 @@ class Query(ObjectType):
         assert not result.errors
         assert result.data == {"reporters": [{"firstName": "Debra"}]}
 
+    def test_resolve_list_external_resolver_async(self):
+        """Resolving a plain list from external resolver should work (and not call get_queryset)"""
+
+        class Reporter(DjangoObjectType):
+            class Meta:
+                model = ReporterModel
+                fields = ("first_name", "articles")
+
+            @classmethod
+            def get_queryset(cls, queryset, info):
+                # Only get reporters with at least 1 article
+                return queryset.annotate(article_count=Count("articles")).filter(
+                    article_count__gt=0
+                )
+
+        @sync_to_async
+        def resolve_reporters(_, info):
+            return [ReporterModel.objects.get(first_name="Debra")]
+
+        class Query(ObjectType):
+            reporters = DjangoListField(Reporter, resolver=resolve_reporters)
+
+        schema = Schema(query=Query)
+
+        query = """
+            query {
+                reporters {
+                    firstName
+                }
+            }
+        """
+
+        r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
+        ReporterModel.objects.create(first_name="Debra", last_name="Payne")
+
+        ArticleModel.objects.create(
+            headline="Amazing news",
+            reporter=r1,
+            pub_date=datetime.date.today(),
+            pub_date_time=datetime.datetime.now(),
+            editor=r1,
+        )
+
+        result = async_to_sync(schema.execute_async)(query)
+
+        assert not result.errors
+        assert result.data == {"reporters": [{"firstName": "Debra"}]}
+
     def test_get_queryset_filter_external_resolver(self):
         class Reporter(DjangoObjectType):
             class Meta:
@@ -505,6 +642,7 @@ class Query(ObjectType):
 
         assert not result.errors
         assert result.data == {"reporters": [{"firstName": "Tara"}]}
+        assert_async_result_equal(schema, query, result)
 
     def test_select_related_and_prefetch_related_are_respected(
         self, django_assert_num_queries
@@ -647,3 +785,4 @@ def resolve_articles(root, info):
             r'SELECT .* FROM "tests_film" INNER JOIN "tests_film_reporters" .* LEFT OUTER JOIN "tests_filmdetails"',
             captured.captured_queries[1]["sql"],
         )
+        assert_async_result_equal(schema, query, result)

From 76eeea4e78ba31e61201a95b9c16c1d4fb1a0546 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 5 May 2023 16:19:13 +0100
Subject: [PATCH 17/35] Update tests for queries

---
 graphene_django/tests/async_test_helper.py |   4 +-
 graphene_django/tests/test_query.py        | 169 +++++++++++++++++++--
 2 files changed, 162 insertions(+), 11 deletions(-)

diff --git a/graphene_django/tests/async_test_helper.py b/graphene_django/tests/async_test_helper.py
index 0487f8918..5785c6c1e 100644
--- a/graphene_django/tests/async_test_helper.py
+++ b/graphene_django/tests/async_test_helper.py
@@ -1,6 +1,6 @@
 from asgiref.sync import async_to_sync
 
 
-def assert_async_result_equal(schema, query, result):
-    async_result = async_to_sync(schema.execute_async)(query)
+def assert_async_result_equal(schema, query, result, **kwargs):
+    async_result = async_to_sync(schema.execute_async)(query, **kwargs)
     assert async_result == result
diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py
index 383ff2e33..19343afd9 100644
--- a/graphene_django/tests/test_query.py
+++ b/graphene_django/tests/test_query.py
@@ -7,6 +7,7 @@
 from django.utils.functional import SimpleLazyObject
 from graphql_relay import to_global_id
 from pytest import raises
+from asgiref.sync import sync_to_async, async_to_sync
 
 import graphene
 from graphene.relay import Node
@@ -16,6 +17,7 @@
 from ..types import DjangoObjectType
 from ..utils import DJANGO_FILTER_INSTALLED
 from .models import Article, CNNReporter, Film, FilmDetails, Person, Pet, Reporter
+from .async_test_helper import assert_async_result_equal
 
 
 def test_should_query_only_fields():
@@ -34,6 +36,7 @@ class Meta:
         """
         result = schema.execute(query)
         assert not result.errors
+        assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_simplelazy_objects():
@@ -59,6 +62,7 @@ def resolve_reporter(self, info):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == {"reporter": {"id": "1"}}
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_wrapped_simplelazy_objects():
@@ -84,6 +88,7 @@ def resolve_reporter(self, info):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == {"reporter": {"id": "1"}}
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_well():
@@ -112,6 +117,7 @@ def resolve_reporter(self, info):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 @pytest.mark.skipif(IntegerRangeField is MissingType, reason="RangeField should exist")
@@ -167,6 +173,7 @@ def resolve_event(self, info):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_node():
@@ -248,6 +255,7 @@ def resolve_reporter(self, info):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_onetoone_fields():
@@ -306,6 +314,7 @@ def resolve_film_details(root, info):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_connectionfields():
@@ -344,6 +353,7 @@ def resolve_all_reporters(self, info, **args):
             "edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}],
         }
     }
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_keep_annotations():
@@ -403,6 +413,7 @@ def resolve_all_articles(self, info, **args):
     """
     result = schema.execute(query)
     assert not result.errors
+    assert_async_result_equal(schema, query, result)
 
 
 @pytest.mark.skipif(
@@ -484,6 +495,7 @@ class Query(graphene.ObjectType):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 @pytest.mark.skipif(
@@ -529,6 +541,7 @@ def resolve_films(self, info, **args):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 @pytest.mark.skipif(
@@ -618,6 +631,7 @@ class Query(graphene.ObjectType):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_enforce_first_or_last(graphene_settings):
@@ -658,6 +672,7 @@ class Query(graphene.ObjectType):
         "paginate the `allReporters` connection.\n"
     )
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_error_if_first_is_greater_than_max(graphene_settings):
@@ -700,6 +715,7 @@ class Query(graphene.ObjectType):
         "exceeds the `first` limit of 100 records.\n"
     )
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_error_if_last_is_greater_than_max(graphene_settings):
@@ -742,6 +758,7 @@ class Query(graphene.ObjectType):
         "exceeds the `last` limit of 100 records.\n"
     )
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_promise_connectionfields():
@@ -777,6 +794,7 @@ def resolve_all_reporters(self, info, **args):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_connectionfields_with_last():
@@ -814,6 +832,7 @@ def resolve_all_reporters(self, info, **args):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_connectionfields_with_manager():
@@ -855,6 +874,7 @@ def resolve_all_reporters(self, info, **args):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_query_dataloader_fields():
@@ -957,6 +977,108 @@ class Query(graphene.ObjectType):
     assert result.data == expected
 
 
+def test_should_query_dataloader_fields_async():
+    from promise import Promise
+    from promise.dataloader import DataLoader
+
+    def article_batch_load_fn(keys):
+        queryset = Article.objects.filter(reporter_id__in=keys)
+        return Promise.resolve(
+            [
+                [article for article in queryset if article.reporter_id == id]
+                for id in keys
+            ]
+        )
+
+    article_loader = DataLoader(article_batch_load_fn)
+
+    class ArticleType(DjangoObjectType):
+        class Meta:
+            model = Article
+            interfaces = (Node,)
+            fields = "__all__"
+
+    class ReporterType(DjangoObjectType):
+        class Meta:
+            model = Reporter
+            interfaces = (Node,)
+            use_connection = True
+            fields = "__all__"
+
+        articles = DjangoConnectionField(ArticleType)
+
+        @staticmethod
+        @sync_to_async
+        def resolve_articles(self, info, **args):
+            return article_loader.load(self.id).get()
+
+    class Query(graphene.ObjectType):
+        all_reporters = DjangoConnectionField(ReporterType)
+
+    r = Reporter.objects.create(
+        first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
+    )
+
+    Article.objects.create(
+        headline="Article Node 1",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="es",
+    )
+    Article.objects.create(
+        headline="Article Node 2",
+        pub_date=datetime.date.today(),
+        pub_date_time=datetime.datetime.now(),
+        reporter=r,
+        editor=r,
+        lang="en",
+    )
+
+    schema = graphene.Schema(query=Query)
+    query = """
+        query ReporterPromiseConnectionQuery {
+            allReporters(first: 1) {
+                edges {
+                    node {
+                        id
+                        articles(first: 2) {
+                            edges {
+                                node {
+                                    headline
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    """
+
+    expected = {
+        "allReporters": {
+            "edges": [
+                {
+                    "node": {
+                        "id": "UmVwb3J0ZXJUeXBlOjE=",
+                        "articles": {
+                            "edges": [
+                                {"node": {"headline": "Article Node 1"}},
+                                {"node": {"headline": "Article Node 2"}},
+                            ]
+                        },
+                    }
+                }
+            ]
+        }
+    }
+
+    result = async_to_sync(schema.execute_async)(query)
+    assert not result.errors
+    assert result.data == expected
+
+
 def test_should_handle_inherited_choices():
     class BaseModel(models.Model):
         choice_field = models.IntegerField(choices=((0, "zero"), (1, "one")))
@@ -1063,6 +1185,7 @@ class Query(graphene.ObjectType):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_resolve_get_queryset_connectionfields():
@@ -1108,6 +1231,7 @@ class Query(graphene.ObjectType):
     result = schema.execute(query)
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_connection_should_limit_after_to_list_length():
@@ -1145,6 +1269,7 @@ class Query(graphene.ObjectType):
     expected = {"allReporters": {"edges": []}}
     assert not result.errors
     assert result.data == expected
+    assert_async_result_equal(schema, query, result, variable_values=dict(after=after))
 
 
 REPORTERS = [
@@ -1188,6 +1313,7 @@ class Query(graphene.ObjectType):
     result = schema.execute(query)
     assert not result.errors
     assert len(result.data["allReporters"]["edges"]) == 4
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_have_next_page(graphene_settings):
@@ -1226,6 +1352,7 @@ class Query(graphene.ObjectType):
     assert not result.errors
     assert len(result.data["allReporters"]["edges"]) == 4
     assert result.data["allReporters"]["pageInfo"]["hasNextPage"]
+    assert_async_result_equal(schema, query, result, variable_values={})
 
     last_result = result.data["allReporters"]["pageInfo"]["endCursor"]
     result2 = schema.execute(query, variable_values=dict(first=4, after=last_result))
@@ -1239,6 +1366,9 @@ class Query(graphene.ObjectType):
     assert {to_global_id("ReporterType", reporter.id) for reporter in db_reporters} == {
         gql_reporter["node"]["id"] for gql_reporter in gql_reporters
     }
+    assert_async_result_equal(
+        schema, query, result2, variable_values=dict(first=4, after=last_result)
+    )
 
 
 @pytest.mark.parametrize("max_limit", [100, 4])
@@ -1262,7 +1392,7 @@ class Query(graphene.ObjectType):
 
     def test_query_last(self, graphene_settings, max_limit):
         schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_last = """
+        query = """
             query {
                 allReporters(last: 3) {
                     edges {
@@ -1274,16 +1404,17 @@ def test_query_last(self, graphene_settings, max_limit):
             }
         """
 
-        result = schema.execute(query_last)
+        result = schema.execute(query)
         assert not result.errors
         assert len(result.data["allReporters"]["edges"]) == 3
         assert [
             e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
         ] == ["First 3", "First 4", "First 5"]
+        assert_async_result_equal(schema, query, result)
 
     def test_query_first_and_last(self, graphene_settings, max_limit):
         schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_first_and_last = """
+        query = """
             query {
                 allReporters(first: 4, last: 3) {
                     edges {
@@ -1295,16 +1426,17 @@ def test_query_first_and_last(self, graphene_settings, max_limit):
             }
         """
 
-        result = schema.execute(query_first_and_last)
+        result = schema.execute(query)
         assert not result.errors
         assert len(result.data["allReporters"]["edges"]) == 3
         assert [
             e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
         ] == ["First 1", "First 2", "First 3"]
+        assert_async_result_equal(schema, query, result)
 
     def test_query_first_last_and_after(self, graphene_settings, max_limit):
         schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_first_last_and_after = """
+        query = """
             query queryAfter($after: String) {
                 allReporters(first: 4, last: 3, after: $after) {
                     edges {
@@ -1318,7 +1450,7 @@ def test_query_first_last_and_after(self, graphene_settings, max_limit):
 
         after = base64.b64encode(b"arrayconnection:0").decode()
         result = schema.execute(
-            query_first_last_and_after,
+            query,
             variable_values=dict(after=after),
         )
         assert not result.errors
@@ -1326,10 +1458,13 @@ def test_query_first_last_and_after(self, graphene_settings, max_limit):
         assert [
             e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
         ] == ["First 2", "First 3", "First 4"]
+        assert_async_result_equal(
+            schema, query, result, variable_values=dict(after=after)
+        )
 
     def test_query_last_and_before(self, graphene_settings, max_limit):
         schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_first_last_and_after = """
+        query = """
             query queryAfter($before: String) {
                 allReporters(last: 1, before: $before) {
                     edges {
@@ -1342,20 +1477,24 @@ def test_query_last_and_before(self, graphene_settings, max_limit):
         """
 
         result = schema.execute(
-            query_first_last_and_after,
+            query,
         )
         assert not result.errors
         assert len(result.data["allReporters"]["edges"]) == 1
         assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 5"
+        assert_async_result_equal(schema, query, result)
 
         before = base64.b64encode(b"arrayconnection:5").decode()
         result = schema.execute(
-            query_first_last_and_after,
+            query,
             variable_values=dict(before=before),
         )
         assert not result.errors
         assert len(result.data["allReporters"]["edges"]) == 1
         assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 4"
+        assert_async_result_equal(
+            schema, query, result, variable_values=dict(before=before)
+        )
 
 
 def test_should_preserve_prefetch_related(django_assert_num_queries):
@@ -1410,6 +1549,7 @@ def resolve_films(root, info, **kwargs):
     with django_assert_num_queries(3) as captured:
         result = schema.execute(query)
         assert not result.errors
+    assert_async_result_equal(schema, query, result)
 
 
 def test_should_preserve_annotations():
@@ -1465,6 +1605,7 @@ def resolve_films(root, info, **kwargs):
     }
     assert result.data == expected, str(result.data)
     assert not result.errors
+    assert_async_result_equal(schema, query, result)
 
 
 def test_connection_should_enable_offset_filtering():
@@ -1504,6 +1645,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_connection_should_enable_offset_filtering_higher_than_max_limit(
@@ -1548,6 +1690,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
+    assert_async_result_equal(schema, query, result)
 
 
 def test_connection_should_forbid_offset_filtering_with_before():
@@ -1578,6 +1721,9 @@ class Query(graphene.ObjectType):
     expected_error = "You can't provide a `before` value at the same time as an `offset` value to properly paginate the `allReporters` connection."
     assert len(result.errors) == 1
     assert result.errors[0].message == expected_error
+    assert_async_result_equal(
+        schema, query, result, variable_values=dict(before=before)
+    )
 
 
 def test_connection_should_allow_offset_filtering_with_after():
@@ -1620,6 +1766,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
+    assert_async_result_equal(schema, query, result, variable_values=dict(after=after))
 
 
 def test_connection_should_succeed_if_last_higher_than_number_of_objects():
@@ -1650,6 +1797,7 @@ class Query(graphene.ObjectType):
     assert not result.errors
     expected = {"allReporters": {"edges": []}}
     assert result.data == expected
+    assert_async_result_equal(schema, query, result, variable_values=dict(last=2))
 
     Reporter.objects.create(first_name="John", last_name="Doe")
     Reporter.objects.create(first_name="Some", last_name="Guy")
@@ -1667,6 +1815,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
+    assert_async_result_equal(schema, query, result, variable_values=dict(last=2))
 
     result = schema.execute(query, variable_values=dict(last=4))
     assert not result.errors
@@ -1681,6 +1830,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
+    assert_async_result_equal(schema, query, result, variable_values=dict(last=4))
 
     result = schema.execute(query, variable_values=dict(last=20))
     assert not result.errors
@@ -1695,6 +1845,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
+    assert_async_result_equal(schema, query, result, variable_values=dict(last=20))
 
 
 def test_should_query_nullable_foreign_key():

From c501fdb20b60ec328033639f11b2fc7daaa57990 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 5 May 2023 16:25:33 +0100
Subject: [PATCH 18/35] swap back to python 3.11

---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index adb54c7a1..9214d35eb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
 default_language_version:
-  python: python3.10
+  python: python3.11
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v4.4.0

From 58b92e6ed3e505b57a46162d42b23b2ea2d56353 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Fri, 5 May 2023 16:28:40 +0100
Subject: [PATCH 19/35] linting

---
 graphene_django/filter/fields.py           | 2 ++
 graphene_django/rest_framework/mutation.py | 6 ++++--
 graphene_django/types.py                   | 5 +++--
 setup.py                                   | 2 +-
 4 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py
index c62ee9ca9..a4ae821ba 100644
--- a/graphene_django/filter/fields.py
+++ b/graphene_django/filter/fields.py
@@ -95,6 +95,7 @@ def filter_kwargs():
         qs = super().resolve_queryset(connection, iterable, info, args)
 
         if info.is_awaitable(qs):
+
             async def filter_async(qs):
                 filterset = filterset_class(
                     data=filter_kwargs(), queryset=await qs, request=info.context
@@ -102,6 +103,7 @@ async def filter_async(qs):
                 if await sync_to_async(filterset.is_valid)():
                     return filterset.qs
                 raise ValidationError(filterset.form.errors.as_json())
+
             return filter_async(qs)
 
         filterset = filterset_class(
diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py
index 8cdd4701c..46d3593c1 100644
--- a/graphene_django/rest_framework/mutation.py
+++ b/graphene_django/rest_framework/mutation.py
@@ -154,17 +154,19 @@ def mutate_and_get_payload(cls, root, info, **input):
         kwargs = cls.get_serializer_kwargs(root, info, **input)
         serializer = cls._meta.serializer_class(**kwargs)
 
-        try: 
+        try:
             get_running_loop()
         except RuntimeError:
-                pass
+            pass
         else:
+
             async def perform_mutate_async():
                 if await sync_to_async(serializer.is_valid)():
                     return await sync_to_async(cls.perform_mutate)(serializer, info)
                 else:
                     errors = ErrorType.from_errors(serializer.errors)
                     return cls(errors=errors)
+
             return perform_mutate_async()
 
         if serializer.is_valid():
diff --git a/graphene_django/types.py b/graphene_django/types.py
index 068d2680d..a064b9046 100644
--- a/graphene_django/types.py
+++ b/graphene_django/types.py
@@ -288,14 +288,15 @@ def get_queryset(cls, queryset, info):
     def get_node(cls, info, id):
         queryset = cls.get_queryset(cls._meta.model.objects, info)
         try:
-            try: 
+            try:
                 import asyncio
+
                 asyncio.get_running_loop()
             except RuntimeError:
                 pass
             else:
                 return queryset.aget(pk=id)
-            
+
             return queryset.get(pk=id)
         except cls._meta.model.DoesNotExist:
             return None
diff --git a/setup.py b/setup.py
index 64273e2a8..56a13b412 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@
     "pytz",
     "django-filter>=22.1",
     "pytest-django>=4.5.2",
-    "pytest-asyncio>=0.16,<2"
+    "pytest-asyncio>=0.16,<2",
 ] + rest_framework_require
 
 

From 791209f5574719962b102cdf32399262fd2f3cb3 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 9 May 2023 21:52:55 +0100
Subject: [PATCH 20/35] Rejig concept to use middleware

---
 graphene_django/converter.py        | 32 ++---------------------------
 graphene_django/debug/middleware.py | 27 ++++++++++++++++++++++--
 2 files changed, 27 insertions(+), 32 deletions(-)

diff --git a/graphene_django/converter.py b/graphene_django/converter.py
index 760973f09..375d68312 100644
--- a/graphene_django/converter.py
+++ b/graphene_django/converter.py
@@ -1,7 +1,5 @@
 from collections import OrderedDict
 from functools import singledispatch, wraps
-from asyncio import get_running_loop
-from asgiref.sync import sync_to_async
 
 from django.db import models
 from django.utils.encoding import force_str
@@ -267,20 +265,7 @@ def dynamic_type():
         if not _type:
             return
 
-        class CustomField(Field):
-            def wrap_resolve(self, parent_resolver):
-                resolver = super().wrap_resolve(parent_resolver)
-
-                try:
-                    get_running_loop()
-                except RuntimeError:
-                    pass
-                else:
-                    resolver = sync_to_async(resolver)
-
-                return resolver
-
-        return CustomField(_type, required=not field.null)
+        return Field(_type, required=not field.null)
 
     return Dynamic(dynamic_type)
 
@@ -335,20 +320,7 @@ def dynamic_type():
         if not _type:
             return
 
-        class CustomField(Field):
-            def wrap_resolve(self, parent_resolver):
-                resolver = super().wrap_resolve(parent_resolver)
-
-                try:
-                    get_running_loop()
-                except RuntimeError:
-                    pass
-                else:
-                    resolver = sync_to_async(resolver)
-
-                return resolver
-
-        return CustomField(
+        return Field(
             _type,
             description=get_django_field_description(field),
             required=not field.null,
diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index d3052a14a..40a951a98 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -1,7 +1,7 @@
 from django.db import connections
 
-from promise import Promise
-
+from asgiref.sync import sync_to_async
+import inspect
 from .sql.tracking import unwrap_cursor, wrap_cursor
 from .exception.formating import wrap_exception
 from .types import DjangoDebug
@@ -69,3 +69,26 @@ def resolve(self, next, root, info, **args):
             return context.django_debug.on_resolve_error(e)
         context.django_debug.add_result(result)
         return result
+
+
+class DjangoSyncRequiredMiddleware:
+    def resolve(self, next, root, info, **args):
+        parent_type = info.parent_type
+
+        ## Anytime the parent is a DjangoObject type
+        # and we're resolving a sync field, we need to wrap it in a sync_to_async
+        if hasattr(parent_type, "graphene_type") and hasattr(
+            parent_type.graphene_type._meta, "model"
+        ):
+            if not inspect.iscoroutinefunction(next):
+                return sync_to_async(next)(root, info, **args)
+
+        ## In addition, if we're resolving to a DjangoObject type
+        # we likely need to wrap it in a sync_to_async as well
+        if hasattr(info.return_type, "graphene_type") and hasattr(
+            info.return_type.graphene_type._meta, "model"
+        ):
+            if not info.is_awaitable(next):
+                return sync_to_async(next)(root, info, **args)
+
+        return next(root, info, **args)

From b69476f50f0442c892a826329317f787a144b20b Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 9 May 2023 22:58:15 +0100
Subject: [PATCH 21/35] improve async detection

---
 graphene_django/debug/middleware.py | 8 ++++++--
 graphene_django/fields.py           | 7 +++----
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index 40a951a98..74366ddcc 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -80,7 +80,9 @@ def resolve(self, next, root, info, **args):
         if hasattr(parent_type, "graphene_type") and hasattr(
             parent_type.graphene_type._meta, "model"
         ):
-            if not inspect.iscoroutinefunction(next):
+            if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
+                next
+            ):
                 return sync_to_async(next)(root, info, **args)
 
         ## In addition, if we're resolving to a DjangoObject type
@@ -88,7 +90,9 @@ def resolve(self, next, root, info, **args):
         if hasattr(info.return_type, "graphene_type") and hasattr(
             info.return_type.graphene_type._meta, "model"
         ):
-            if not info.is_awaitable(next):
+            if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
+                next
+            ):
                 return sync_to_async(next)(root, info, **args)
 
         return next(root, info, **args)
diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 99e84c76b..43225c293 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -266,11 +266,10 @@ async def resolve_connection_async(iterable):
                 iterable = await iterable
                 if iterable is None:
                     iterable = default_manager
-                ## This could also be async
-                iterable = queryset_resolver(connection, iterable, info, args)
 
-                if info.is_awaitable(iterable):
-                    iterable = await iterable
+                iterable = await sync_to_async(queryset_resolver)(
+                    connection, iterable, info, args
+                )
 
                 return await sync_to_async(cls.resolve_connection)(
                     connection, args, iterable, max_limit=max_limit

From b134ab0a3e74aa35436aa25f62bcee0dcfbfa35b Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 9 May 2023 23:27:02 +0100
Subject: [PATCH 22/35] Handle custom Djangoconnectionresolvers

---
 graphene_django/debug/middleware.py | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index 74366ddcc..c0a316b6e 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -95,4 +95,15 @@ def resolve(self, next, root, info, **args):
             ):
                 return sync_to_async(next)(root, info, **args)
 
+        ## We also need to handle custom resolvers around Connections
+        # but only when their parent is not already a DjangoObject type
+        # this case already gets handled above.
+        if hasattr(info.return_type, "graphene_type"):
+            if hasattr(info.return_type.graphene_type, "Edge"):
+                node_type = info.return_type.graphene_type.Edge.node.type
+                if hasattr(node_type, "_meta") and hasattr(node_type._meta, "model"):
+                    if not inspect.iscoroutinefunction(
+                        next
+                    ) and not inspect.isasyncgenfunction(next):
+                        return sync_to_async(next)(root, info, **args)
         return next(root, info, **args)

From 8c068fbc2b9a5b388022405af21b701a29ecc3fc Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Wed, 10 May 2023 19:17:30 +0100
Subject: [PATCH 23/35] Update to pull out mutations and ensure that
 DjangoFields don't get double sync'd

---
 graphene_django/debug/middleware.py | 30 ++++++++++++++++------
 graphene_django/fields.py           | 40 +++++++++++++++++++----------
 2 files changed, 48 insertions(+), 22 deletions(-)

diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index c0a316b6e..f8ea42ec3 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -5,6 +5,9 @@
 from .sql.tracking import unwrap_cursor, wrap_cursor
 from .exception.formating import wrap_exception
 from .types import DjangoDebug
+from graphql.type.definition import GraphQLNonNull
+
+from django.db.models import QuerySet
 
 
 class DjangoDebugContext:
@@ -74,6 +77,12 @@ def resolve(self, next, root, info, **args):
 class DjangoSyncRequiredMiddleware:
     def resolve(self, next, root, info, **args):
         parent_type = info.parent_type
+        return_type = info.return_type
+
+        if isinstance(parent_type, GraphQLNonNull):
+            parent_type = parent_type.of_type
+        if isinstance(return_type, GraphQLNonNull):
+            return_type = return_type.of_type
 
         ## Anytime the parent is a DjangoObject type
         # and we're resolving a sync field, we need to wrap it in a sync_to_async
@@ -87,23 +96,28 @@ def resolve(self, next, root, info, **args):
 
         ## In addition, if we're resolving to a DjangoObject type
         # we likely need to wrap it in a sync_to_async as well
-        if hasattr(info.return_type, "graphene_type") and hasattr(
-            info.return_type.graphene_type._meta, "model"
+        if hasattr(return_type, "graphene_type") and hasattr(
+            return_type.graphene_type._meta, "model"
         ):
             if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
                 next
             ):
                 return sync_to_async(next)(root, info, **args)
 
-        ## We also need to handle custom resolvers around Connections
-        # but only when their parent is not already a DjangoObject type
-        # this case already gets handled above.
-        if hasattr(info.return_type, "graphene_type"):
-            if hasattr(info.return_type.graphene_type, "Edge"):
-                node_type = info.return_type.graphene_type.Edge.node.type
+        ## We can move this resolver logic into the field resolver itself and probably should
+        if hasattr(return_type, "graphene_type"):
+            if hasattr(return_type.graphene_type, "Edge"):
+                node_type = return_type.graphene_type.Edge.node.type
                 if hasattr(node_type, "_meta") and hasattr(node_type._meta, "model"):
                     if not inspect.iscoroutinefunction(
                         next
                     ) and not inspect.isasyncgenfunction(next):
                         return sync_to_async(next)(root, info, **args)
+
+        if info.parent_type.name == "Mutation":
+            if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
+                next
+            ):
+                return sync_to_async(next)(root, info, **args)
+
         return next(root, info, **args)
diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 43225c293..8f4ce5f47 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -1,3 +1,4 @@
+import inspect
 from functools import partial
 
 from django.db.models.query import QuerySet
@@ -82,13 +83,6 @@ async def resolve_list_async(iterable):
             # Pass queryset to the DjangoObjectType get_queryset method
             queryset = maybe_queryset(django_object_type.get_queryset(queryset, info))
 
-        try:
-            get_running_loop()
-        except RuntimeError:
-            pass
-        else:
-            return sync_to_async(list)(queryset)
-
         return queryset
 
     def wrap_resolve(self, parent_resolver):
@@ -97,12 +91,31 @@ def wrap_resolve(self, parent_resolver):
         if isinstance(_type, NonNull):
             _type = _type.of_type
         django_object_type = _type.of_type.of_type
-        return partial(
-            self.list_resolver,
-            django_object_type,
-            resolver,
-            self.get_manager(),
-        )
+
+        try:
+            get_running_loop()
+        except RuntimeError:
+            return partial(
+                self.list_resolver, django_object_type, resolver, self.get_manager()
+            )
+        else:
+            if not inspect.iscoroutinefunction(
+                resolver
+            ) and not inspect.isasyncgenfunction(resolver):
+                async_resolver = sync_to_async(resolver)
+
+            ## This is needed because our middleware can't detect the resolver as async when we returns partial[couroutine]
+            async def wrapped_resolver(root, info, **args):
+                return await self.list_resolver(
+                    django_object_type,
+                    async_resolver,
+                    self.get_manager(),
+                    root,
+                    info,
+                    **args
+                )
+
+            return wrapped_resolver
 
 
 class DjangoConnectionField(ConnectionField):
@@ -257,7 +270,6 @@ def connection_resolver(
 
         # eventually leads to DjangoObjectType's get_queryset (accepts queryset)
         # or a resolve_foo (does not accept queryset)
-
         iterable = resolver(root, info, **args)
 
         if info.is_awaitable(iterable):

From d3f8fcf906818add9fb849e5f5467ddf42000fa1 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 16:07:49 +0100
Subject: [PATCH 24/35] handle connections without middleware

---
 graphene_django/debug/middleware.py | 10 ----------
 graphene_django/fields.py           | 26 ++++++++++++++++++++++----
 2 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index f8ea42ec3..50b0cfea9 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -104,16 +104,6 @@ def resolve(self, next, root, info, **args):
             ):
                 return sync_to_async(next)(root, info, **args)
 
-        ## We can move this resolver logic into the field resolver itself and probably should
-        if hasattr(return_type, "graphene_type"):
-            if hasattr(return_type.graphene_type, "Edge"):
-                node_type = return_type.graphene_type.Edge.node.type
-                if hasattr(node_type, "_meta") and hasattr(node_type._meta, "model"):
-                    if not inspect.iscoroutinefunction(
-                        next
-                    ) and not inspect.isasyncgenfunction(next):
-                        return sync_to_async(next)(root, info, **args)
-
         if info.parent_type.name == "Mutation":
             if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
                 next
diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 8f4ce5f47..081d87012 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -270,6 +270,17 @@ def connection_resolver(
 
         # eventually leads to DjangoObjectType's get_queryset (accepts queryset)
         # or a resolve_foo (does not accept queryset)
+
+        try:
+            get_running_loop()
+        except RuntimeError:
+            pass
+        else:
+            if not inspect.iscoroutinefunction(
+                resolver
+            ) and not inspect.isasyncgenfunction(resolver):
+                resolver = sync_to_async(resolver)
+
         iterable = resolver(root, info, **args)
 
         if info.is_awaitable(iterable):
@@ -293,17 +304,24 @@ async def resolve_connection_async(iterable):
             iterable = default_manager
         # thus the iterable gets refiltered by resolve_queryset
         # but iterable might be promise
-        iterable = queryset_resolver(connection, iterable, info, args)
 
         try:
             get_running_loop()
         except RuntimeError:
             pass
         else:
-            return sync_to_async(cls.resolve_connection)(
-                connection, args, iterable, max_limit=max_limit
-            )
 
+            async def perform_resolve(iterable):
+                iterable = await sync_to_async(queryset_resolver)(
+                    connection, iterable, info, args
+                )
+                return await sync_to_async(cls.resolve_connection)(
+                    connection, args, iterable, max_limit=max_limit
+                )
+
+            return perform_resolve(iterable)
+
+        iterable = queryset_resolver(connection, iterable, info, args)
         return cls.resolve_connection(connection, args, iterable, max_limit=max_limit)
 
     def wrap_resolve(self, parent_resolver):

From 930248f78d15cb7dc7dd8ba0e45a811de3359d77 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 16:20:13 +0100
Subject: [PATCH 25/35] Refactor out async helper functions

---
 graphene_django/debug/middleware.py        | 14 ++++-------
 graphene_django/fields.py                  | 27 +++++-----------------
 graphene_django/rest_framework/mutation.py |  8 ++-----
 graphene_django/types.py                   |  9 ++------
 graphene_django/utils/__init__.py          |  4 ++++
 graphene_django/utils/utils.py             | 16 +++++++++++++
 6 files changed, 35 insertions(+), 43 deletions(-)

diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index 50b0cfea9..3e66a8f08 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -9,6 +9,8 @@
 
 from django.db.models import QuerySet
 
+from ..utils import is_sync_function
+
 
 class DjangoDebugContext:
     def __init__(self):
@@ -89,9 +91,7 @@ def resolve(self, next, root, info, **args):
         if hasattr(parent_type, "graphene_type") and hasattr(
             parent_type.graphene_type._meta, "model"
         ):
-            if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
-                next
-            ):
+            if is_sync_function(next):
                 return sync_to_async(next)(root, info, **args)
 
         ## In addition, if we're resolving to a DjangoObject type
@@ -99,15 +99,11 @@ def resolve(self, next, root, info, **args):
         if hasattr(return_type, "graphene_type") and hasattr(
             return_type.graphene_type._meta, "model"
         ):
-            if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
-                next
-            ):
+            if is_sync_function(next):
                 return sync_to_async(next)(root, info, **args)
 
         if info.parent_type.name == "Mutation":
-            if not inspect.iscoroutinefunction(next) and not inspect.isasyncgenfunction(
-                next
-            ):
+            if is_sync_function(next):
                 return sync_to_async(next)(root, info, **args)
 
         return next(root, info, **args)
diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 081d87012..db846b9da 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -11,7 +11,6 @@
 )
 
 from asgiref.sync import sync_to_async
-from asyncio import get_running_loop
 
 from graphene import Int, NonNull
 from graphene.relay import ConnectionField
@@ -19,7 +18,7 @@
 from graphene.types import Field, List
 
 from .settings import graphene_settings
-from .utils import maybe_queryset
+from .utils import maybe_queryset, is_sync_function, is_running_async
 
 
 class DjangoListField(Field):
@@ -92,16 +91,12 @@ def wrap_resolve(self, parent_resolver):
             _type = _type.of_type
         django_object_type = _type.of_type.of_type
 
-        try:
-            get_running_loop()
-        except RuntimeError:
+        if not is_running_async():
             return partial(
                 self.list_resolver, django_object_type, resolver, self.get_manager()
             )
         else:
-            if not inspect.iscoroutinefunction(
-                resolver
-            ) and not inspect.isasyncgenfunction(resolver):
+            if is_sync_function(resolver):
                 async_resolver = sync_to_async(resolver)
 
             ## This is needed because our middleware can't detect the resolver as async when we returns partial[couroutine]
@@ -271,14 +266,8 @@ def connection_resolver(
         # eventually leads to DjangoObjectType's get_queryset (accepts queryset)
         # or a resolve_foo (does not accept queryset)
 
-        try:
-            get_running_loop()
-        except RuntimeError:
-            pass
-        else:
-            if not inspect.iscoroutinefunction(
-                resolver
-            ) and not inspect.isasyncgenfunction(resolver):
+        if is_running_async():
+            if is_sync_function(resolver):
                 resolver = sync_to_async(resolver)
 
         iterable = resolver(root, info, **args)
@@ -305,11 +294,7 @@ async def resolve_connection_async(iterable):
         # thus the iterable gets refiltered by resolve_queryset
         # but iterable might be promise
 
-        try:
-            get_running_loop()
-        except RuntimeError:
-            pass
-        else:
+        if is_running_async():
 
             async def perform_resolve(iterable):
                 iterable = await sync_to_async(queryset_resolver)(
diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py
index 46d3593c1..ca2764d0b 100644
--- a/graphene_django/rest_framework/mutation.py
+++ b/graphene_django/rest_framework/mutation.py
@@ -2,7 +2,6 @@
 
 from django.shortcuts import get_object_or_404
 from rest_framework import serializers
-from asyncio import get_running_loop
 from asgiref.sync import sync_to_async
 
 import graphene
@@ -13,6 +12,7 @@
 
 from ..types import ErrorType
 from .serializer_converter import convert_serializer_field
+from ..utils import is_running_async
 
 
 class SerializerMutationOptions(MutationOptions):
@@ -154,11 +154,7 @@ def mutate_and_get_payload(cls, root, info, **input):
         kwargs = cls.get_serializer_kwargs(root, info, **input)
         serializer = cls._meta.serializer_class(**kwargs)
 
-        try:
-            get_running_loop()
-        except RuntimeError:
-            pass
-        else:
+        if is_running_async():
 
             async def perform_mutate_async():
                 if await sync_to_async(serializer.is_valid)():
diff --git a/graphene_django/types.py b/graphene_django/types.py
index a064b9046..7216cdee3 100644
--- a/graphene_django/types.py
+++ b/graphene_django/types.py
@@ -16,6 +16,7 @@
     camelize,
     get_model_fields,
     is_valid_django_model,
+    is_running_async,
 )
 
 ALL_FIELDS = "__all__"
@@ -288,13 +289,7 @@ def get_queryset(cls, queryset, info):
     def get_node(cls, info, id):
         queryset = cls.get_queryset(cls._meta.model.objects, info)
         try:
-            try:
-                import asyncio
-
-                asyncio.get_running_loop()
-            except RuntimeError:
-                pass
-            else:
+            if is_running_async():
                 return queryset.aget(pk=id)
 
             return queryset.get(pk=id)
diff --git a/graphene_django/utils/__init__.py b/graphene_django/utils/__init__.py
index 671b0609a..7344ce59a 100644
--- a/graphene_django/utils/__init__.py
+++ b/graphene_django/utils/__init__.py
@@ -6,6 +6,8 @@
     get_reverse_fields,
     is_valid_django_model,
     maybe_queryset,
+    is_sync_function,
+    is_running_async,
 )
 
 __all__ = [
@@ -16,4 +18,6 @@
     "camelize",
     "is_valid_django_model",
     "GraphQLTestCase",
+    "is_sync_function",
+    "is_running_async",
 ]
diff --git a/graphene_django/utils/utils.py b/graphene_django/utils/utils.py
index 343a3a74c..beac3e61a 100644
--- a/graphene_django/utils/utils.py
+++ b/graphene_django/utils/utils.py
@@ -1,4 +1,5 @@
 import inspect
+from asyncio import get_running_loop
 
 from django.db import connection, models, transaction
 from django.db.models.manager import Manager
@@ -105,3 +106,18 @@ def set_rollback():
     atomic_requests = connection.settings_dict.get("ATOMIC_REQUESTS", False)
     if atomic_requests and connection.in_atomic_block:
         transaction.set_rollback(True)
+
+
+def is_running_async():
+    try:
+        get_running_loop()
+    except RuntimeError:
+        return False
+    else:
+        return True
+
+
+def is_sync_function(func):
+    return not inspect.iscoroutinefunction(func) and not inspect.isasyncgenfunction(
+        func
+    )

From c27dd6c732d5e1b923a67763ede6af5eef02a10c Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 17:03:24 +0100
Subject: [PATCH 26/35] cleanup middleware

---
 graphene_django/debug/middleware.py | 24 ++++++++----------------
 1 file changed, 8 insertions(+), 16 deletions(-)

diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index 3e66a8f08..1e7bcf600 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -86,24 +86,16 @@ def resolve(self, next, root, info, **args):
         if isinstance(return_type, GraphQLNonNull):
             return_type = return_type.of_type
 
-        ## Anytime the parent is a DjangoObject type
-        # and we're resolving a sync field, we need to wrap it in a sync_to_async
-        if hasattr(parent_type, "graphene_type") and hasattr(
-            parent_type.graphene_type._meta, "model"
+        if any(
+            [
+                hasattr(parent_type, "graphene_type")
+                and hasattr(parent_type.graphene_type._meta, "model"),
+                hasattr(return_type, "graphene_type")
+                and hasattr(return_type.graphene_type._meta, "model"),
+                info.parent_type.name == "Mutation",
+            ]
         ):
             if is_sync_function(next):
                 return sync_to_async(next)(root, info, **args)
 
-        ## In addition, if we're resolving to a DjangoObject type
-        # we likely need to wrap it in a sync_to_async as well
-        if hasattr(return_type, "graphene_type") and hasattr(
-            return_type.graphene_type._meta, "model"
-        ):
-            if is_sync_function(next):
-                return sync_to_async(next)(root, info, **args)
-
-        if info.parent_type.name == "Mutation":
-            if is_sync_function(next):
-                return sync_to_async(next)(root, info, **args)
-
         return next(root, info, **args)

From fba274dc4dd28993655fbf770a0dfcb0f2cb7b1b Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 17:09:15 +0100
Subject: [PATCH 27/35] refactor middleware

---
 graphene_django/types.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/graphene_django/types.py b/graphene_django/types.py
index 7216cdee3..2ce9db309 100644
--- a/graphene_django/types.py
+++ b/graphene_django/types.py
@@ -293,6 +293,7 @@ def get_node(cls, info, id):
                 return queryset.aget(pk=id)
 
             return queryset.get(pk=id)
+
         except cls._meta.model.DoesNotExist:
             return None
 

From 84ba7d775a7356bc62e0ce5eec9190d3f163c056 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 17:46:23 +0100
Subject: [PATCH 28/35] Remove unused path

---
 graphene_django/fields.py | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index db846b9da..ce50d0b1d 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -293,19 +293,6 @@ async def resolve_connection_async(iterable):
             iterable = default_manager
         # thus the iterable gets refiltered by resolve_queryset
         # but iterable might be promise
-
-        if is_running_async():
-
-            async def perform_resolve(iterable):
-                iterable = await sync_to_async(queryset_resolver)(
-                    connection, iterable, info, args
-                )
-                return await sync_to_async(cls.resolve_connection)(
-                    connection, args, iterable, max_limit=max_limit
-                )
-
-            return perform_resolve(iterable)
-
         iterable = queryset_resolver(connection, iterable, info, args)
         return cls.resolve_connection(connection, args, iterable, max_limit=max_limit)
 

From 848536ee3a8dc7a9eb13e66646375d447a2bff58 Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 19:45:02 +0100
Subject: [PATCH 29/35] Clean up wrapped list resolver

---
 graphene_django/fields.py | 37 +++++++++++++------------------------
 1 file changed, 13 insertions(+), 24 deletions(-)

diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index ce50d0b1d..f58082b48 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -49,10 +49,14 @@ def model(self):
     def get_manager(self):
         return self.model._default_manager
 
-    @staticmethod
+    @classmethod
     def list_resolver(
-        django_object_type, resolver, default_manager, root, info, **args
+        cls, django_object_type, resolver, default_manager, root, info, **args
     ):
+        if is_running_async():
+            if is_sync_function(resolver):
+                resolver = sync_to_async(resolver)
+
         iterable = resolver(root, info, **args)
 
         if info.is_awaitable(iterable):
@@ -82,7 +86,7 @@ async def resolve_list_async(iterable):
             # Pass queryset to the DjangoObjectType get_queryset method
             queryset = maybe_queryset(django_object_type.get_queryset(queryset, info))
 
-        return queryset
+        return list(queryset)
 
     def wrap_resolve(self, parent_resolver):
         resolver = super().wrap_resolve(parent_resolver)
@@ -90,27 +94,12 @@ def wrap_resolve(self, parent_resolver):
         if isinstance(_type, NonNull):
             _type = _type.of_type
         django_object_type = _type.of_type.of_type
-
-        if not is_running_async():
-            return partial(
-                self.list_resolver, django_object_type, resolver, self.get_manager()
-            )
-        else:
-            if is_sync_function(resolver):
-                async_resolver = sync_to_async(resolver)
-
-            ## This is needed because our middleware can't detect the resolver as async when we returns partial[couroutine]
-            async def wrapped_resolver(root, info, **args):
-                return await self.list_resolver(
-                    django_object_type,
-                    async_resolver,
-                    self.get_manager(),
-                    root,
-                    info,
-                    **args
-                )
-
-            return wrapped_resolver
+        return partial(
+            self.list_resolver,
+            django_object_type,
+            resolver,
+            self.get_manager(),
+        )
 
 
 class DjangoConnectionField(ConnectionField):

From 2659d67fd08c648f6ea31fbc0a9fefc47565a3bf Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 20:19:35 +0100
Subject: [PATCH 30/35] updates tests

---
 graphene_django/tests/test_fields.py | 5 -----
 graphene_django/tests/test_query.py  | 2 --
 2 files changed, 7 deletions(-)

diff --git a/graphene_django/tests/test_fields.py b/graphene_django/tests/test_fields.py
index 2a5055398..0ec6e82fd 100644
--- a/graphene_django/tests/test_fields.py
+++ b/graphene_django/tests/test_fields.py
@@ -156,8 +156,6 @@ class Meta:
         class Query(ObjectType):
             reporters = DjangoListField(Reporter)
 
-            @staticmethod
-            @sync_to_async
             def resolve_reporters(_, info):
                 return ReporterModel.objects.filter(first_name="Tara")
 
@@ -411,8 +409,6 @@ def get_queryset(cls, queryset, info):
         class Query(ObjectType):
             reporters = DjangoListField(Reporter)
 
-            @staticmethod
-            @sync_to_async
             def resolve_reporters(_, info):
                 return [ReporterModel.objects.get(first_name="Debra")]
 
@@ -565,7 +561,6 @@ def get_queryset(cls, queryset, info):
                     article_count__gt=0
                 )
 
-        @sync_to_async
         def resolve_reporters(_, info):
             return [ReporterModel.objects.get(first_name="Debra")]
 
diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py
index 19343afd9..951ca79cb 100644
--- a/graphene_django/tests/test_query.py
+++ b/graphene_django/tests/test_query.py
@@ -1007,8 +1007,6 @@ class Meta:
 
         articles = DjangoConnectionField(ArticleType)
 
-        @staticmethod
-        @sync_to_async
         def resolve_articles(self, info, **args):
             return article_loader.load(self.id).get()
 

From 37ebd6309b65d05067220b402d583f815cf98e2e Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Tue, 16 May 2023 20:26:05 +0100
Subject: [PATCH 31/35] follow main removing validate call

---
 graphene_django/views.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/graphene_django/views.py b/graphene_django/views.py
index 7933c64c9..7386e6d5b 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -665,10 +665,6 @@ async def execute_graphql_request(
                     )
                 )
 
-        validation_errors = validate(self.schema.graphql_schema, document)
-        if validation_errors:
-            return ExecutionResult(data=None, errors=validation_errors)
-
         try:
             extra_options = {}
             if self.execution_context_class:

From 5a1911118247f5704e8d0e5e2060039c6cc65c80 Mon Sep 17 00:00:00 2001
From: Paul Craciunoiu <paul@craciunoiu.net>
Date: Thu, 25 May 2023 11:44:39 -0600
Subject: [PATCH 32/35] Fix graphiql for async

---
 graphene_django/views.py | 24 ++----------------------
 1 file changed, 2 insertions(+), 22 deletions(-)

diff --git a/graphene_django/views.py b/graphene_django/views.py
index 7386e6d5b..8cdc1d118 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -403,28 +403,6 @@ def get_content_type(request):
 
 
 class AsyncGraphQLView(GraphQLView):
-    graphiql_template = "graphene/graphiql.html"
-
-    # Polyfill for window.fetch.
-    whatwg_fetch_version = "3.6.2"
-    whatwg_fetch_sri = "sha256-+pQdxwAcHJdQ3e/9S4RK6g8ZkwdMgFQuHvLuN5uyk5c="
-
-    # React and ReactDOM.
-    react_version = "17.0.2"
-    react_sri = "sha256-Ipu/TQ50iCCVZBUsZyNJfxrDk0E2yhaEIz0vqI+kFG8="
-    react_dom_sri = "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0="
-
-    # The GraphiQL React app.
-    graphiql_version = "1.4.7"  # "1.0.3"
-    graphiql_sri = "sha256-cpZ8w9D/i6XdEbY/Eu7yAXeYzReVw0mxYd7OU3gUcsc="  # "sha256-VR4buIDY9ZXSyCNFHFNik6uSe0MhigCzgN4u7moCOTk="
-    graphiql_css_sri = "sha256-HADQowUuFum02+Ckkv5Yu5ygRoLllHZqg0TFZXY7NHI="  # "sha256-LwqxjyZgqXDYbpxQJ5zLQeNcf7WVNSJ+r8yp2rnWE/E="
-
-    # The websocket transport library for subscriptions.
-    subscriptions_transport_ws_version = "0.9.18"
-    subscriptions_transport_ws_sri = (
-        "sha256-i0hAXd4PdJ/cHX3/8tIy/Q/qKiWr5WSTxMFuL9tACkw="
-    )
-
     schema = None
     graphiql = False
     middleware = None
@@ -512,6 +490,8 @@ async def dispatch(self, request, *args, **kwargs):
                     graphiql_css_sri=self.graphiql_css_sri,
                     subscriptions_transport_ws_version=self.subscriptions_transport_ws_version,
                     subscriptions_transport_ws_sri=self.subscriptions_transport_ws_sri,
+                    graphiql_plugin_explorer_version=self.graphiql_plugin_explorer_version,
+                    graphiql_plugin_explorer_sri=self.graphiql_plugin_explorer_sri,
                     # The SUBSCRIPTION_PATH setting.
                     subscription_path=self.subscription_path,
                     # GraphiQL headers tab,

From 28bc8584440fd17cc3cc8741a1038ff10b6be00b Mon Sep 17 00:00:00 2001
From: Firas Kafri <firas.alkafri@gmail.com>
Date: Thu, 10 Aug 2023 09:53:04 +0300
Subject: [PATCH 33/35] resolve conflicts and fix format

---
 examples/cookbook/cookbook/recipes/schema.py |  3 +-
 examples/cookbook/cookbook/urls.py           |  4 +--
 graphene_django/debug/middleware.py          | 12 +++----
 graphene_django/fields.py                    |  7 ++--
 graphene_django/filter/fields.py             |  3 +-
 graphene_django/rest_framework/mutation.py   |  4 +--
 graphene_django/tests/test_fields.py         |  6 ++--
 graphene_django/tests/test_query.py          | 36 +++++++++++---------
 graphene_django/types.py                     |  2 +-
 graphene_django/utils/__init__.py            |  4 +--
 graphene_django/utils/utils.py               |  1 +
 graphene_django/views.py                     |  6 ++--
 12 files changed, 40 insertions(+), 48 deletions(-)

diff --git a/examples/cookbook/cookbook/recipes/schema.py b/examples/cookbook/cookbook/recipes/schema.py
index 19fbcc6eb..2f2fc032e 100644
--- a/examples/cookbook/cookbook/recipes/schema.py
+++ b/examples/cookbook/cookbook/recipes/schema.py
@@ -2,8 +2,7 @@
 
 from asgiref.sync import sync_to_async
 
-from cookbook.recipes.models import Recipe, RecipeIngredient
-from graphene import Node, String, Field
+from graphene import Field, Node, String
 from graphene_django.filter import DjangoFilterConnectionField
 from graphene_django.types import DjangoObjectType
 
diff --git a/examples/cookbook/cookbook/urls.py b/examples/cookbook/cookbook/urls.py
index 57687daa6..c0f6fdf65 100644
--- a/examples/cookbook/cookbook/urls.py
+++ b/examples/cookbook/cookbook/urls.py
@@ -1,8 +1,8 @@
-from django.urls import re_path
 from django.contrib import admin
+from django.urls import re_path
 from django.views.decorators.csrf import csrf_exempt
+
 from graphene_django.views import AsyncGraphQLView
-from graphene_django.views import GraphQLView
 
 urlpatterns = [
     re_path(r"^admin/", admin.site.urls),
diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index 1e7bcf600..16589ebe9 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -1,15 +1,11 @@
-from django.db import connections
-
 from asgiref.sync import sync_to_async
-import inspect
-from .sql.tracking import unwrap_cursor, wrap_cursor
-from .exception.formating import wrap_exception
-from .types import DjangoDebug
+from django.db import connections
 from graphql.type.definition import GraphQLNonNull
 
-from django.db.models import QuerySet
-
 from ..utils import is_sync_function
+from .exception.formating import wrap_exception
+from .sql.tracking import unwrap_cursor, wrap_cursor
+from .types import DjangoDebug
 
 
 class DjangoDebugContext:
diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 50a80dcb2..d5ed46804 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -1,6 +1,6 @@
-import inspect
 from functools import partial
 
+from asgiref.sync import sync_to_async
 from django.db.models.query import QuerySet
 from graphql_relay import (
     connection_from_array_slice,
@@ -9,16 +9,13 @@
     offset_to_cursor,
 )
 
-from asgiref.sync import sync_to_async
-from promise import Promise
-
 from graphene import Int, NonNull
 from graphene.relay import ConnectionField
 from graphene.relay.connection import connection_adapter, page_info_adapter
 from graphene.types import Field, List
 
 from .settings import graphene_settings
-from .utils import maybe_queryset, is_sync_function, is_running_async
+from .utils import is_running_async, is_sync_function, maybe_queryset
 
 
 class DjangoListField(Field):
diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py
index 4c8d82614..a7be72076 100644
--- a/graphene_django/filter/fields.py
+++ b/graphene_django/filter/fields.py
@@ -1,14 +1,13 @@
 from collections import OrderedDict
 from functools import partial
 
+from asgiref.sync import sync_to_async
 from django.core.exceptions import ValidationError
 
 from graphene.types.argument import to_arguments
 from graphene.types.enum import EnumType
 from graphene.utils.str_converters import to_snake_case
 
-from asgiref.sync import sync_to_async
-
 from ..fields import DjangoConnectionField
 from .utils import get_filtering_args_from_filterset, get_filterset_class
 
diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py
index 105729955..f5f9b4eb9 100644
--- a/graphene_django/rest_framework/mutation.py
+++ b/graphene_django/rest_framework/mutation.py
@@ -1,9 +1,9 @@
 from collections import OrderedDict
 from enum import Enum
 
+from asgiref.sync import sync_to_async
 from django.shortcuts import get_object_or_404
 from rest_framework import serializers
-from asgiref.sync import sync_to_async
 
 import graphene
 from graphene.relay.mutation import ClientIDMutation
@@ -12,8 +12,8 @@
 from graphene.types.objecttype import yank_fields_from_attrs
 
 from ..types import ErrorType
-from .serializer_converter import convert_serializer_field
 from ..utils import is_running_async
+from .serializer_converter import convert_serializer_field
 
 
 class SerializerMutationOptions(MutationOptions):
diff --git a/graphene_django/tests/test_fields.py b/graphene_django/tests/test_fields.py
index eae5d5b9d..3f67a9f4e 100644
--- a/graphene_django/tests/test_fields.py
+++ b/graphene_django/tests/test_fields.py
@@ -1,21 +1,21 @@
 import datetime
 import re
-from django.db.models import Count, Prefetch
-from asgiref.sync import sync_to_async, async_to_sync
+
 import pytest
+from asgiref.sync import async_to_sync
 from django.db.models import Count, Prefetch
 
 from graphene import List, NonNull, ObjectType, Schema, String
 
 from ..fields import DjangoListField
 from ..types import DjangoObjectType
+from .async_test_helper import assert_async_result_equal
 from .models import (
     Article as ArticleModel,
     Film as FilmModel,
     FilmDetails as FilmDetailsModel,
     Reporter as ReporterModel,
 )
-from .async_test_helper import assert_async_result_equal
 
 
 class TestDjangoListField:
diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py
index 4dd83e9fb..e8aa1f746 100644
--- a/graphene_django/tests/test_query.py
+++ b/graphene_django/tests/test_query.py
@@ -2,12 +2,12 @@
 import datetime
 
 import pytest
+from asgiref.sync import async_to_sync
 from django.db import models
 from django.db.models import Q
 from django.utils.functional import SimpleLazyObject
 from graphql_relay import to_global_id
 from pytest import raises
-from asgiref.sync import sync_to_async, async_to_sync
 
 import graphene
 from graphene.relay import Node
@@ -28,6 +28,7 @@
     Reporter,
 )
 
+
 def test_should_query_only_fields():
     with raises(Exception):
 
@@ -1569,7 +1570,7 @@ class Query(graphene.ObjectType):
     expected = {"allReporters": {"edges": []}}
     assert not result.errors
     assert result.data == expected
-    assert_async_result_equal(schema, query, result, variable_values=dict(after=after))
+    assert_async_result_equal(schema, query, result, variable_values={"after": after})
 
 
 REPORTERS = [
@@ -1667,7 +1668,7 @@ class Query(graphene.ObjectType):
         gql_reporter["node"]["id"] for gql_reporter in gql_reporters
     }
     assert_async_result_equal(
-        schema, query, result2, variable_values=dict(first=4, after=last_result)
+        schema, query, result2, variable_values={"first": 4, "after": last_result}
     )
 
 
@@ -1736,7 +1737,7 @@ def test_query_first_and_last(self, graphene_settings, max_limit):
 
     def test_query_first_last_and_after(self, graphene_settings, max_limit):
         schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query = """
+        query_first_last_and_after = """
             query queryAfter($after: String) {
                 allReporters(first: 4, last: 3, after: $after) {
                     edges {
@@ -1759,12 +1760,12 @@ def test_query_first_last_and_after(self, graphene_settings, max_limit):
             e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
         ] == ["First 2", "First 3", "First 4"]
         assert_async_result_equal(
-            schema, query, result, variable_values=dict(after=after)
+            schema, query_first_last_and_after, result, variable_values={"after": after}
         )
 
     def test_query_last_and_before(self, graphene_settings, max_limit):
         schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query = """
+        query_first_last_and_after = """
             query queryAfter($before: String) {
                 allReporters(last: 1, before: $before) {
                     edges {
@@ -1777,12 +1778,12 @@ def test_query_last_and_before(self, graphene_settings, max_limit):
         """
 
         result = schema.execute(
-            query,
+            query_first_last_and_after,
         )
         assert not result.errors
         assert len(result.data["allReporters"]["edges"]) == 1
         assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 5"
-        assert_async_result_equal(schema, query, result)
+        assert_async_result_equal(schema, query_first_last_and_after, result)
 
         before = base64.b64encode(b"arrayconnection:5").decode()
         result = schema.execute(
@@ -1793,7 +1794,10 @@ def test_query_last_and_before(self, graphene_settings, max_limit):
         assert len(result.data["allReporters"]["edges"]) == 1
         assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 4"
         assert_async_result_equal(
-            schema, query, result, variable_values=dict(before=before)
+            schema,
+            query_first_last_and_after,
+            result,
+            variable_values={"before": before},
         )
 
 
@@ -2021,9 +2025,7 @@ class Query(graphene.ObjectType):
     expected_error = "You can't provide a `before` value at the same time as an `offset` value to properly paginate the `allReporters` connection."
     assert len(result.errors) == 1
     assert result.errors[0].message == expected_error
-    assert_async_result_equal(
-        schema, query, result, variable_values=dict(before=before)
-    )
+    assert_async_result_equal(schema, query, result, variable_values={"before": before})
 
 
 def test_connection_should_allow_offset_filtering_with_after():
@@ -2066,7 +2068,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
-    assert_async_result_equal(schema, query, result, variable_values=dict(after=after))
+    assert_async_result_equal(schema, query, result, variable_values={"after": after})
 
 
 def test_connection_should_succeed_if_last_higher_than_number_of_objects():
@@ -2097,7 +2099,7 @@ class Query(graphene.ObjectType):
     assert not result.errors
     expected = {"allReporters": {"edges": []}}
     assert result.data == expected
-    assert_async_result_equal(schema, query, result, variable_values=dict(last=2))
+    assert_async_result_equal(schema, query, result, variable_values={"last": 2})
 
     Reporter.objects.create(first_name="John", last_name="Doe")
     Reporter.objects.create(first_name="Some", last_name="Guy")
@@ -2115,7 +2117,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
-    assert_async_result_equal(schema, query, result, variable_values=dict(last=2))
+    assert_async_result_equal(schema, query, result, variable_values={"last": 2})
 
     result = schema.execute(query, variable_values={"last": 4})
     assert not result.errors
@@ -2130,7 +2132,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
-    assert_async_result_equal(schema, query, result, variable_values=dict(last=4))
+    assert_async_result_equal(schema, query, result, variable_values={"last": 4})
 
     result = schema.execute(query, variable_values={"last": 20})
     assert not result.errors
@@ -2145,7 +2147,7 @@ class Query(graphene.ObjectType):
         }
     }
     assert result.data == expected
-    assert_async_result_equal(schema, query, result, variable_values=dict(last=20))
+    assert_async_result_equal(schema, query, result, variable_values={"last": 20})
 
 
 def test_should_query_nullable_foreign_key():
diff --git a/graphene_django/types.py b/graphene_django/types.py
index 5dfbe5509..0f94fa271 100644
--- a/graphene_django/types.py
+++ b/graphene_django/types.py
@@ -16,8 +16,8 @@
     DJANGO_FILTER_INSTALLED,
     camelize,
     get_model_fields,
-    is_valid_django_model,
     is_running_async,
+    is_valid_django_model,
 )
 
 ALL_FIELDS = "__all__"
diff --git a/graphene_django/utils/__init__.py b/graphene_django/utils/__init__.py
index b1a11cf49..609da967c 100644
--- a/graphene_django/utils/__init__.py
+++ b/graphene_django/utils/__init__.py
@@ -5,10 +5,10 @@
     camelize,
     get_model_fields,
     get_reverse_fields,
+    is_running_async,
+    is_sync_function,
     is_valid_django_model,
     maybe_queryset,
-    is_sync_function,
-    is_running_async,
 )
 
 __all__ = [
diff --git a/graphene_django/utils/utils.py b/graphene_django/utils/utils.py
index 1b9760a56..7cdaf06b7 100644
--- a/graphene_django/utils/utils.py
+++ b/graphene_django/utils/utils.py
@@ -153,6 +153,7 @@ def is_sync_function(func):
         func
     )
 
+
 def bypass_get_queryset(resolver):
     """
     Adds a bypass_get_queryset attribute to the resolver, which is used to
diff --git a/graphene_django/views.py b/graphene_django/views.py
index 1f3a90b1d..2a63d23e1 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -2,16 +2,14 @@
 import json
 import re
 import traceback
-
-from asyncio import gather, coroutines
+from asyncio import coroutines, gather
 
 from django.db import connection, transaction
 from django.http import HttpResponse, HttpResponseNotAllowed
 from django.http.response import HttpResponseBadRequest
 from django.shortcuts import render
-from django.utils.decorators import method_decorator
+from django.utils.decorators import classonlymethod, method_decorator
 from django.views.decorators.csrf import ensure_csrf_cookie
-from django.utils.decorators import classonlymethod
 from django.views.generic import View
 from graphql import OperationType, get_operation_ast, parse
 from graphql.error import GraphQLError

From 45fb299398adfb68dedbecb828915efc55797a1d Mon Sep 17 00:00:00 2001
From: Josh Warwick <josh.warwick15@gmail.com>
Date: Wed, 16 Aug 2023 11:00:45 +0100
Subject: [PATCH 34/35] Fix bug when running sync view under asgi

---
 graphene_django/debug/middleware.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index 16589ebe9..adc8d0404 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -2,7 +2,7 @@
 from django.db import connections
 from graphql.type.definition import GraphQLNonNull
 
-from ..utils import is_sync_function
+from ..utils import is_running_async, is_sync_function
 from .exception.formating import wrap_exception
 from .sql.tracking import unwrap_cursor, wrap_cursor
 from .types import DjangoDebug
@@ -91,7 +91,7 @@ def resolve(self, next, root, info, **args):
                 info.parent_type.name == "Mutation",
             ]
         ):
-            if is_sync_function(next):
+            if is_sync_function(next) and is_running_async():
                 return sync_to_async(next)(root, info, **args)
 
         return next(root, info, **args)

From c2d601c0e2ee151ae918a9c01578e5e2f17dea6f Mon Sep 17 00:00:00 2001
From: Firas Kafri <3097061+firaskafri@users.noreply.github.com>
Date: Fri, 9 Feb 2024 11:58:35 +0300
Subject: [PATCH 35/35] Fix newline

---
 graphene_django/tests/test_fields.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/graphene_django/tests/test_fields.py b/graphene_django/tests/test_fields.py
index eac62ddaa..3b63c3aa7 100644
--- a/graphene_django/tests/test_fields.py
+++ b/graphene_django/tests/test_fields.py
@@ -881,4 +881,4 @@ class Query(ObjectType):
             TypeError,
             match="DjangoConnectionField only accepts DjangoObjectType types as underlying type",
         ):
-            Schema(query=Query)
\ No newline at end of file
+            Schema(query=Query)