Package: graphql-sync-dataloaders
graphql-sync-dataloadersUse DataLoaders in your Python GraphQL servers that have to run in a sync
context (i.e. Django).RequirementsPython 3.8+graphql-core >=3.2.0InstallationThis package can be installed fromPyPiby running:pipinstallgraphql-sync-dataloadersStrawberry setupWhen creating your Strawberry Schema passDeferredExecutionContextas theexecution_context_classargument:# schema.pyimportstrawberryfromgraphql_sync_dataloadersimportDeferredExecutionContextschema=strawberry.Schema(Query,execution_context_class=DeferredExecutionContext)Then create your dataloaders using theSyncDataLoaderclass:fromtypingimportListfromgraphql_sync_dataloadersimportSyncDataLoaderfrom.appimportmodels# your Django modelsdefload_users(keys:List[int])->List[User]:qs=models.User.objects.filter(id__in=keys)user_map={user.id:userforuserinqs}return[user_map.get(key,None)forkeyinkeys]user_loader=SyncDataLoader(load_users)You can then use the loader in your resolvers and it will automatically be
batched to reduce the number of SQL queries:importstrawberry@strawberry.typeclassQuery:@strawberry.fielddefget_user(self,id:strawberry.ID)->User:returnuser_loader.load(id)Note: You probably want to setup your loaders in context. Seehttps://strawberry.rocks/docs/guides/dataloaders#usage-with-contextfor more
detailsThe following query will only make 1 SQL query:fragmentUserDetailsonUser{username}query{user1:getUser(id:'1'){...UserDetails}user2:getUser(id:'2'){...UserDetails}user3:getUser(id:'3'){...UserDetails}}Graphene-Django setupRequires graphene-django >=3.0.0b8When setting up your GraphQLView passDeferredExecutionContextas theexecution_context_classargument:# urls.pyfromdjango.urlsimportpathfromgraphene_django.viewsimportGraphQLViewfromgraphql_sync_dataloadersimportDeferredExecutionContextfrom.schemaimportschemaurlpatterns=[path("graphql",csrf_exempt(GraphQLView.as_view(schema=schema,execution_context_class=DeferredExecutionContext)),),]Then create your dataloaders using theSyncDataLoaderclass:fromtypingimportListfromgraphql_sync_dataloadersimportSyncDataLoaderfrom.appimportmodels# your Django modelsdefload_users(keys:List[int])->List[User]:qs=models.User.objects.filter(id__in=keys)user_map={user.id:userforuserinqs}return[user_map.get(key,None)forkeyinkeys]user_loader=SyncDataLoader(load_users)You can then use the loader in your resolvers and it will automatically be
batched to reduce the number of SQL queries:importgrapheneclassQuery(graphene.ObjectType):get_user=graphene.Field(User,id=graphene.ID)defresolve_get_user(root,info,id):returnuser_loader.load(id)The following query will only make 1 SQL query:fragmentUserDetailsonUser{username}query{user1:getUser(id:'1'){...UserDetails}user2:getUser(id:'2'){...UserDetails}user3:getUser(id:'3'){...UserDetails}}How it worksThis library implements a custom version of the graphql-coreExecutionContext classthat is aware of theSyncFutureobjects defined in this library. ASyncFuturerepresents a value that hasn't been resolved to a value yet
(similiar to asycnio Futures or JavaScript Promises) and that is what theSyncDataLoaderreturns when you call the.loadfunction.When the customExecutionContextencounters aSyncFuturethat gets returned
from a resolver and it keeps track of them. Then after the first pass of the
exection it triggers theSyncFuturecallbacks until there are none left. Once
there are none left the data is fully resolved and can be returned to the
caller synchronously. This allows us to implement aDataLoaderpattern that
batches calls to a loader function, and it allows us to do this in a fully
synchronously way.Credits@Citofor graphql-core and for implementing the first version of this inhttps://github.com/graphql-python/graphql-core/pull/155