Skip to main content

A set of tools for using dataloaders with Django and Strawberry GraphQL.

Project description

Dataloaders for Django and Strawberry

A set of tools for using dataloaders with Django and Strawberry without unnecessary boilerplate.

Installation

pip install strawberry-django-dataloaders

Usage & examples

This package provides 3 levels of generating dataloaders, each offering higher level of abstraction than the previous one.

View

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=...)),
]

Models definition

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")

Level 1: Simple dataloader

On the first level, we're defining and using different dataloader for each relationship.

One-to-one and Many-to-one relationship

  1. Define the dataloaders
from strawberry_django_dataloaders import dataloaders

class ColorPKDataLoader(dataloaders.BasicPKDataLoader):
    model = models.Color


class FruitPlantPKDataLoader(dataloaders.BasicPKDataLoader):
    model = models.FruitPlant
  1. 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)

One-to-many relationship

  1. 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
  1. 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)

Level 2: Dataloader factories

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)

Level 3: Auto dataloader field

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()

Contributing

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 pytest

Pre commit

We have a configuration for pre-commit, to add the hook run the following command:

pre-commit install

Links

Known issues/shortcomings

  • Many-to-many relation is currently not supported.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

strawberry_django_dataloaders-0.2.0.tar.gz (8.4 kB view hashes)

Uploaded Source

Built Distribution

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page