question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

DataLoader batch_load_fn + load_many not batching in graphene

See original GitHub issue

This test case doesn’t appear to work when using graphene:

https://github.com/syrusakbary/promise/blob/master/tests/test_dataloader.py#L32

The batch load function is called with one key at a time when calling dataloader.load_many([6, 7]). I wasn’t able to make that test case break by running it many times or with large (100,000) array sizes, which is why I suspect it’s due to an interaction with graphene. It works in v2.0.2 (the pip install promise version), but not promise/master.

Here is roughly the code that I’m running:

class HeroLoader(DataLoader):
    def batch_load_fn(self, friend_ids):
        import logging; logging.error(friend_ids)  ########### Console log #2 here
        url = '{}/?id={}'.format(
            Hero.endpoint,
            ','.join([str(id) for id in friend_ids])
        )
        response = requests.get(url)
        results = response.json()['results']
        return Promise.resolve(results)

        
class Hero(graphene.ObjectType):
    endpoint = '{}/heroes'.format(HOST)
    data_loader = HeroLoader()

    id = graphene.Int()
    name = graphene.String(name='name')
    friend_ids = graphene.List(graphene.Int)
    friends = graphene.List(partial(lambda: Hero))

    def resolve_friends(self, args, context, info):
        import logging; logging.error(self.friend_ids)           ######### Console log #1 here
        heroes_json = self.data_loader.load_many(self.friend_ids)
        return heroes_json.then(do_stuff_with_promise......)

The console output of an infringing request looks like this (and stepping through with a debugger yields the same results):

ERROR:root:[6, 7]
ERROR:root:[6]
ERROR:root:[7]

The expected output is

ERROR:root:[6, 7]
ERROR:root:[6, 7]

If you want a test that works with one version of promise, but not the other:

Issue Analytics

  • State:open
  • Created 6 years ago
  • Comments:10

github_iconTop GitHub Comments

3reactions
amzhangcommented, Nov 12, 2020

Try below. It’s copied from our code base with irrelevant parts stripped, so has not been tested.

It uses aiodataloader

Hope this helps.

import asyncio
from promise.promise import Promise
from django.conf import settings
from django.http.response import HttpResponseBadRequest
from django.http import HttpResponseNotAllowed
from graphene_django.views import GraphQLView, HttpError
from graphql.execution import ExecutionResult

class CustomGraphQLView(GraphQLView):
    # Copied from the source code with the following modifications:
    # - Starting event loop before resolve so that asyncio dataloaders can run.
    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:
            backend = self.get_backend(request)
            document = backend.document_from_string(self.schema, query)
        except Exception as e:
            return ExecutionResult(errors=[e], invalid=True)

        if request.method.lower() == "get":
            operation_type = document.get_operation_type(operation_name)
            if operation_type and operation_type != "query":
                if show_graphiql:
                    return None

                raise HttpError(
                    HttpResponseNotAllowed(
                        ["POST"],
                        "Can only perform a {} operation from a POST request.".format(
                            operation_type
                        ),
                    )
                )

        # See: https://docs.python.org/3/whatsnew/3.8.html#asyncio
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        try:
            extra_options = {}
            extra_options["return_promise"] = True
            if self.executor:
                # We only include it optionally since
                # executor is not a valid argument in all backends
                extra_options["executor"] = self.executor

            ret = document.execute(
                root=self.get_root_value(request),
                variables=variables,
                operation_name=operation_name,
                context=self.get_context(request),
                middleware=self.get_middleware(request),
                **extra_options,
            )

            if Promise.is_thenable(ret):
                timeout = settings.GRAPHQL_VIEW["REQUEST_TIMEOUT_AFTER"].total_seconds()
                loop.run_until_complete(asyncio.wait_for(ret.future, timeout))
                return ret.get()
            else:
                return ret
        except Exception as e:  # pylint: disable=broad-except
            return ExecutionResult(errors=[e], invalid=True)

        finally:
            asyncio.set_event_loop(None)
            loop.close()
1reaction
amzhangcommented, Apr 25, 2020

@tazimmerman unfortunately, I don’t know of any workarounds when using threads.

We moved to using aiodataloader with graphene-django, and starting a new asyncio event loop for every request. I’m not sure if that’s the right way to do it, but batching is working.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why does batching with a dataloader not work in a test?
I am using the dataloader library for node.js on a project where I create a graphQL API with Apollo Server. Code. function getClasses(args,...
Read more >
dataloader | Yarn - Package Manager
A data loading utility to reduce requests to a backend via batching and caching ... DataLoader is a generic utility to be used...
Read more >
Dataloader - Graphene-Python
DataLoader is a generic utility to be used as part of your application's data ... Batching is not an advanced feature, it's DataLoader's...
Read more >
Use DataLoader to Batch And Cache Arbitrary MongoDB ...
We implement arbitrary query caching and batching using ... this to support batching any arbitrary MongoDB query—not just queries by ...
Read more >
dataloader - npm
This mechanism of batching and caching data requests is certainly not unique to Node.js or JavaScript, it is also the primary motivation for...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found