Package: graphene-django-optimizer-patch
graphene-django-optimizerOptimize queries executed bygraphene-djangoautomatically, usingselect_related,prefetch_relatedandonlymethods of Django QuerySet.Installpipinstallgraphene-django-optimizerNote: If you are using Graphene V2, please install version0.8. v0.9 and forward will support only Graphene V3UsageHaving the following schema based onthe tutorial of graphene-django(notice the use ofgql_optimizer)# cookbook/ingredients/schema.pyimportgraphenefromgraphene_django.typesimportDjangoObjectTypeimportgraphene_django_optimizerasgql_optimizerfromcookbook.ingredients.modelsimportCategory,IngredientclassCategoryType(DjangoObjectType):classMeta:model=CategoryclassIngredientType(DjangoObjectType):classMeta:model=IngredientclassQuery(graphene.ObjectType):all_categories=graphene.List(CategoryType)all_ingredients=graphene.List(IngredientType)defresolve_all_categories(root,info):returngql_optimizer.query(Category.objects.all(),info)defresolve_all_ingredients(root,info):returngql_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{idnamecategory{idname}}}# optimized queryset:ingredients=(Ingredient.objects.select_related('category').only('id','name','category__id','category__name'))Fetching all the categories with the related ingredients:{allCategories{idnameingredients{idname}}}# optimized queryset:categories=(Category.objects.only('id','name').prefetch_related(Prefetch('ingredients',queryset=Ingredient.objects.only('id','name'),)))Advanced usageSometimes we need to have a custom resolver function. In those cases, the field can't be auto optimized.
So we need to usegql_optimizer.resolver_hintsdecorator to indicate the optimizations.If the resolver returns a model field, we can use themodel_fieldargument:importgrapheneimportgraphene_django_optimizerasgql_optimizerclassItemType(gql_optimizer.OptimizedDjangoObjectType):product=graphene.Field('ProductType')@gql_optimizer.resolver_hints(model_field='product',)defresolve_product(root,info):# check if user have permission for seeing the productifinfo.context.user.is_anonymous():returnNonereturnroot.productThis will automatically optimize any subfield ofproduct.Now, if the resolver uses related fields, you can use theselect_relatedargument:importgrapheneimportgraphene_django_optimizerasgql_optimizerclassItemType(gql_optimizer.OptimizedDjangoObjectType):name=graphene.String()@gql_optimizer.resolver_hints(select_related=('product','shipping'),only=('product__name','shipping__name'),)defresolve_name(root,info):return'{}{}'.format(root.product.name,root.shipping.name)Notice the usage of the typeOptimizedDjangoObjectType, which enables
optimization of any single node queries.Finally, if your field has an argument for filtering results,
you can use theprefetch_relatedargument with a function
that returns aPrefetchinstance as the value.fromdjango.db.modelsimportPrefetchimportgrapheneimportgraphene_django_optimizerasgql_optimizerclassCartType(gql_optimizer.OptimizedDjangoObjectType):items=graphene.List('ItemType',product_id=graphene.ID(),)@gql_optimizer.resolver_hints(prefetch_related=lambdainfo,product_id:Prefetch('items',queryset=gql_optimizer.query(Item.objects.filter(product_id=product_id),info),to_attr='gql_product_id_'+product_id,),)defresolve_items(root,info,product_id):returngetattr(root,'gql_product_id_'+product_id)With these hints, any field can be optimized.Optimize with non model fieldsSometimes 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 usedisable_abort_onlyoption:classIngredientType(gql_optimizer.OptimizedDjangoObjectType):calculated_calories=graphene.String()classMeta:model=Ingredientdefresolve_calculated_calories(root,info):returnget_calories_for_ingredient(root.id)classQuery(object):all_ingredients=graphene.List(IngredientType)defresolve_all_ingredients(root,info):returngql_optimizer.query(Ingredient.objects.all(),info,disable_abort_only=True)AnnotationsThe queryset can be optimized with an annotation when and only if a field is requested. To
do so, the annotate resolver hint can be used.classRecipeType(gql_optimizer.OptimizedDjangoObjectType):ingredient_count=graphene.Int()classMeta:model=Recipefields=('id',)@gql_optimizer.resolver_hints(annotate={'gql_ingredient_count':Count('ingredients')})defresolve_ingredient_count(root,info):returngetattr(root,'gql_ingredient_count')classQuery(object):all_recipes=graphene.List(RecipeType)defresolve_all_recipes(root,info):returngql_optimizer.query(Recipe.objects.all(),info)When using annotations there are two caveats.The queryset will not be annotated if the optimization fails.If an annotation is used in a related query that will usually
result in a optimizedselect_related,prefetch_relatedis used instead,
adding one additional query. See example below.classRecipeType(gql_optimizer.OptimizedDjangoObjectType):ingredient_count=graphene.Int()classMeta:model=Recipefields=('id',)@gql_optimizer.resolver_hints(annotate={'gql_ingredient_count':Count('ingredients')})defresolve_ingredient_count(root,info):returngetattr(root,'gql_ingredient_count')classIngredientType(gql_optimizer.OptimizedDjangoObjectType):recipe=gql_optimizer.field(graphene.Field(RecipeType),model_field='recipe',)classMeta:model=Ingredientfields=('id',name')classQuery(object):all_ingredients=graphene.List(IngredientType)defresolve_all_ingredients(root,info):returngql_optimizer.query(Ingredient.objects.all(),info)The GraphQL query.query {
  allIngredients {
     id
     name
     recipe {
       id
       name
       ingredientCount
     }
  }
}Will resolve in two SQL queries. One to fetch all ingredients, one to prefetch
all recipes for those ingredients.