Skip to content
Snippets Groups Projects
Select Git revision
  • 69eb7ae0079834730aa03aef7f29571abf01f37f
  • master default protected
  • renovate/django-5.x
  • renovate/flake8-7.x
  • renovate/pytest-8.x
  • renovate/pytest-cov-6.x
  • renovate/pytest-runner-6.x
  • renovate/pytest-django-4.x
  • renovate/graphene-django-3.x
  • renovate/mock-5.x
  • renovate/black-25.x
  • renovate/pytest-django-3.x
  • renovate/pytest-cov-2.x
  • renovate/graphene-3.x
  • renovate/flake8-3.x
  • renovate/django-3.x
  • renovate/pytest-4.x
  • 0.9.2
18 results

README.md

Blame
  • Jonathan Weth's avatar
    Jonathan Weth authored
    69eb7ae0
    History

    graphene-django-optimizer

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

    Install

    pip install graphene-django-optimizer

    Usage

    Having the following schema based on the tutorial of graphene-django (notice the use of gql_optimizer)

    # cookbook/ingredients/schema.py
    import graphene
    
    from graphene_django.types import DjangoObjectType
    import graphene_django_optimizer as gql_optimizer
    
    from cookbook.ingredients.models import Category, Ingredient
    
    
    class CategoryType(DjangoObjectType):
        class Meta:
            model = Category
    
    
    class IngredientType(DjangoObjectType):
        class Meta:
            model = Ingredient
    
    
    class Query(graphene.ObjectType):
        all_categories = graphene.List(CategoryType)
        all_ingredients = graphene.List(IngredientType)
    
        def resolve_all_categories(root, info):
            return gql_optimizer.query(Category.objects.all(), info)
    
        def resolve_all_ingredients(root, info):
            return gql_optimizer.query(Ingredient.objects.all(), info)

    We will show some graphql queries and the queryset that will be executed.

    Fetching all the ingredients with the related category:

    {
      allIngredients {
        id
        name
        category {
            id
            name
        }
      }
    }
    # optimized queryset:
    ingredients = (
        Ingredient.objects
        .select_related('category')
        .only('id', 'name', 'category__id', 'category__name')
    )

    Fetching all the categories with the related ingredients:

    {
      allCategories {
        id
        name
        ingredients {
            id
            name
        }
      }
    }
    # optimized queryset:
    categories = (
        Category.objects
        .only('id', 'name')
        .prefetch_related(Prefetch(
            'ingredients',
            queryset=Ingredient.objects.only('id', 'name'),
        ))
    )

    Advanced usage

    Sometimes we need to have a custom resolver function. In those cases, the field can't be auto optimized. So we need to use gql_optimizer.resolver_hints decorator to indicate the optimizations.

    If the resolver returns a model field, we can use the model_field argument:

    import graphene
    import graphene_django_optimizer as gql_optimizer
    
    
    class ItemType(gql_optimizer.OptimizedDjangoObjectType):
        product = graphene.Field('ProductType')
    
        @gql_optimizer.resolver_hints(
            model_field='product',
        )
        def resolve_product(root, info):
            # check if user have permission for seeing the product
            if info.context.user.is_anonymous():
                return None
            return root.product

    This will automatically optimize any subfield of product.

    Now, if the resolver uses related fields, you can use the select_related argument:

    import graphene
    import graphene_django_optimizer as gql_optimizer
    
    
    class ItemType(gql_optimizer.OptimizedDjangoObjectType):
        name = graphene.String()
    
        @gql_optimizer.resolver_hints(
            select_related=('product', 'shipping'),
            only=('product__name', 'shipping__name'),
        )
        def resolve_name(root, info):
            return '{} {}'.format(root.product.name, root.shipping.name)

    Notice the usage of the type OptimizedDjangoObjectType, which enables optimization of any single node queries.

    Finally, if your field has an argument for filtering results, you can use the prefetch_related argument with a function that returns a Prefetch instance as the value.

    from django.db.models import Prefetch
    import graphene
    import graphene_django_optimizer as gql_optimizer
    
    
    class CartType(gql_optimizer.OptimizedDjangoObjectType):
        items = graphene.List(
            'ItemType',
            product_id=graphene.ID(),
        )
    
        @gql_optimizer.resolver_hints(
            prefetch_related=lambda info, product_id: Prefetch(
                'items',
                queryset=gql_optimizer.query(Item.objects.filter(product_id=product_id), info),
                to_attr='gql_product_id_' + product_id,
            ),
        )
        def resolve_items(root, info, product_id):
            return getattr(root, 'gql_product_id_' + product_id)

    With these hints, any field can be optimized.

    Optimize with non model fields

    Sometimes we need to have a custom non model fields. In those cases, the optimizer would not optimize with the Django .only() method. So if we still want to optimize with the .only() method, we need to use disable_abort_only option:

    
    class IngredientType(gql_optimizer.OptimizedDjangoObjectType):
        calculated_calories = graphene.String()
    
        class Meta:
            model = Ingredient
    
        def resolve_calculated_calories(root, info):
            return get_calories_for_ingredient(root.id)
    
    
    class Query(object):
        all_ingredients = graphene.List(IngredientType)
    
        def resolve_all_ingredients(root, info):
            return gql_optimizer.query(Ingredient.objects.all(), info, disable_abort_only=True)