A set of tools for using dataloaders with Django and Strawberry without unnecessary boilerplate.
pip install strawberry-django-dataloadersThis package provides 3 levels of generating dataloaders, each offering higher level of abstraction than the previous one.
In the graphql/ endpoint where you wish to use dataloaders, use (or subclass) DataloaderAsyncGraphQLView.
This is necessary because created dataloaders are stored to the request context. This ensures that:
- fresh dataloader instances are created for each request
- a dataloader persists for the duration of the request
which are both important properties for loaded values caching.
from django.urls import path
from strawberry_django_dataloaders.views import DataloaderAsyncGraphQLView
urlpatterns = [
path('graphql/', DataloaderAsyncGraphQLView.as_view(schema=...)),
]Definition of models used in the examples.
from django.db import models
class Fruit(models.Model):
plant = models.OneToOneField("FruitPlant", ...)
color = models.ForeignKey("Color", ...)
varieties = models.ManyToManyField("FruitVariety", ..., related_name="fruits")
class FruitEater(models.Model):
favourite_fruit = models.ForeignKey("Fruit", ..., related_name="eaters")On the first level, we're defining and using different dataloader for each relationship.
- Define the dataloaders
from strawberry_django_dataloaders import dataloaders
class ColorPKDataLoader(dataloaders.BasicPKDataLoader):
model = models.Color
class FruitPlantPKDataLoader(dataloaders.BasicPKDataLoader):
model = models.FruitPlant- Use them when defining the Strawberry type
@strawberry_django.type(models.Fruit)
class FruitType:
id: strawberry.auto
### ↓ HERE ↓ ###
@strawberry.field
async def color(self: "models.Fruit", info: "Info") -> ColorType | None:
return await dataloaders.ColorPKDataLoader(context=info.context).load(self.color_id)
@strawberry.field
async def plant(self: "models.Fruit", info: "Info") -> FruitPlantType | None:
return await dataloaders.FruitPlantPKDataLoader(context=info.context).load(self.plant_id)- Define the dataloader
from strawberry_django_dataloaders import dataloaders
class FruitEatersReverseFKDataLoader(dataloaders.BasicReverseFKDataLoader):
model = models.FruitEater
reverse_path = "favourite_fruit_id" # <-- is the "reverse" FK field from FruitEater to Fruit model- Use it when defining the Strawberry type
@strawberry_django.type(models.Fruit)
class FruitType:
id: strawberry.auto
### ↓ HERE ↓ ###
@strawberry.field
async def eaters(self: "models.Fruit", info: "Info") -> list[FruitEaterType]:
return await dataloaders.FruitEatersReverseFKDataLoader(context=info.context).load(self.pk)When using the dataloader factories, we no longer need to define a dataloader for each relation.
from strawberry_django_dataloaders import factories
@strawberry_django.type(models.Fruit)
class FruitTypeDataLoaderFactories:
id: strawberry.auto
### ↓ ONE-TO-ONE AND MANY-TO-ONE DATALOADERS ↓ ###
@strawberry.field
async def color(self: "models.Fruit", info: "Info") -> ColorType | None:
loader = factories.PKDataLoaderFactory.get_loader_class("tests.Color")
return await loader(context=info.context).load(self.color_id)
@strawberry.field
async def plant(self: "models.Fruit", info: "Info") -> FruitPlantType | None:
loader = factories.PKDataLoaderFactory.get_loader_class("tests.FruitPlant")
return await loader(context=info.context).load(self.plant_id)
### ↓ ONE-TO-MANY DATALOADER ↓ ###
@strawberry.field
async def eaters(self: "models.Fruit", info: "Info") -> list[FruitEaterType]:
loader = factories.ReverseFKDataLoaderFactory.get_loader_class(
"tests.FruitEater",
reverse_path="favourite_fruit_id",
)
return await loader(context=info.context).load(self.color_id)A field used in a similar fashion as native Django strawberry field, but it has auto-defined correct dataloader handler based on the field relationship.
from strawberry_django_dataloaders import fields
@strawberry_django.type(models.Fruit)
class FruitTypeAutoDataLoaderFields:
id: strawberry.auto
color: ColorType = fields.auto_dataloader_field()
plant: FruitPlantType = fields.auto_dataloader_field()
varieties: list[FruitVarietyType] = fields.auto_dataloader_field()
eaters: list[FruitEaterType] = fields.auto_dataloader_field()Pull requests for any improvements are welcome.
Poetry is used to manage dependencies. To get started follow these steps:
git clone https://github.com/VojtechPetru/strawberry-django-dataloaders
cd strawberry-django-dataloaders
poetry install
poetry run pytestWe have a configuration for pre-commit, to add the hook run the following command:
pre-commit install- Inspired and builds on top of a great article at: https://alexcleduc.com/posts/graphql-dataloader-composition/
- Repository: https://github.com/VojtechPetru/strawberry-django-dataloaders
- Issue tracker: https://github.com/VojtechPetru/strawberry-django-dataloaders/issues. In case of sensitive bugs (e.g. security vulnerabilities) please contact me at petru.vojtech@gmail.com directly.
Many-to-manyrelation is currently not supported.