strawberry-django-optimizer

Automatic query optimization for django and strawberry-graphql


License
MIT
Install
pip install strawberry-django-optimizer==0.1.0

Documentation

strawberry-django-optimizer

PyPI version python version django version

Optimize queries executed by strawberry automatically, using select_related , prefetch_related and only methods of Django QuerySet.

This package is heavily based on graphene-django-optimizer which is intended for use with graphene-django.

Install

pip install strawberry-django-optimizer

Usage

Having the following models based on the strawberry-graphql-django docs:

# models.py
from django.db import models


class Fruit(models.Model):
    name = models.CharField(max_length=20)
    color = models.ForeignKey('Color', blank=True, null=True,
                              related_name='fruits', on_delete=models.CASCADE)


class Color(models.Model):
    name = models.CharField(max_length=20)

And the following schema:

# schema.py
import strawberry
from typing import List
from strawberry.django import auto
from strawberry_django_optimizer import optimized_django_field
from fruits import models


@strawberry.django.type(models.Fruit)
class Fruit:
    id: auto
    name: auto
    color: 'Color'


@strawberry.django.type(models.Color)
class Color:
    id: auto
    name: auto
    fruits: List[Fruit]


@strawberry.type
class Query:
    fruits: List[Fruit] = optimized_django_field()

The following graphql query would cause n + 1 DB queries:

query Fruit {
    fruits {
        id
        name
        color {
            id
            name
        }
    }
}

Since optimized_django_field was used instead of strawberry.django.field the queryset is automatically optimized.

# optimized queryset:
Fruits.objects.select_related('color').only('id', 'name', 'color__id', 'color__name')

Reverse ForeignKey relations also are automatically optimized with prefetch_related:

query Colors {
    colors {
        id
        name
        fruits {
            id
            name
        }
    }
}
# optimized queryset:
Color.objects.only('id', 'name', 'color').prefetch_related(
    Prefetch('fruits', queryset=Fruit.objects.only('id', 'name'))
)

Advanced usage

Use resolver_hint for cases where only, select_related and prefetch_related optimizations can't be inferred automatically. To keep the only optimization when using custom resolver functions, resolver_hints must be used to declare all fields that are accessed or the only optimization will be disabled.

# schema.py
import strawberry
from strawberry.django import auto
from strawberry_django_optimizer import resolver_hints
from fruits import models


@strawberry.django.type(models.Fruit)
class Fruit:
    id: auto
    
    @resolver_hints(only=('name',))
    @strawberry.field
    def name_display(self) -> str:
        return f'My name is: {self.name}'

If the resolver function accesses a related field you can add an optimization hint for select_related:

# schema.py
import strawberry
from strawberry.django import auto
from strawberry_django_optimizer import resolver_hints
from fruits import models


@strawberry.django.type(models.Fruit)
class Fruit:
    id: auto
    
    @resolver_hints(
        select_related=('color',),
        only=('color__name',),
    )
    def color_display(self) -> str:
        return f'My color is: {self.color.name}'

Parameters for resolver_hint

Parameter Usage
model_field If the resolver returns a model field
only Declare all fields that the resolver accesses
select_related If the resolver uses related fields
prefetch_reltaed If the resolver uses related fields

Known issues (ToDo)

  • Inline Fragments can't be optimized
  • Interfaces and Unions are not supported