# graphene-django-optimizer
Optimize queries executed by [graphene-django](https://github.com/graphql-python/graphene-django) automatically, using [`select_related`](https://docs.djangoproject.com/en/2.0/ref/models/querysets/#select-related), [`prefetch_related`](https://docs.djangoproject.com/en/2.0/ref/models/querysets/#prefetch-related) and [`only`](https://docs.djangoproject.com/en/2.0/ref/models/querysets/#only) methods of Django QuerySet.
## Install
```bash
pip install graphene-django-optimizer
```
## Usage
Having the following schema based on [the tutorial of graphene-django](http://docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#hello-graphql-schema-and-object-types) (notice the use of `gql_optimizer`)
```py
# 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:
```graphql
{
allIngredients {
id
name
category {
id
name
}
}
}
```
```py
# optimized queryset:
ingredients = (
Ingredient.objects
.select_related('category')
.only('id', 'name', 'category__id', 'category__name')
)
```
Fetching all the categories with the related ingredients:
```graphql
{
allCategories {
id
name
ingredients {
id
name
}
}
}
```
```py
# 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:
```py
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:
```py
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.
```py
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:
```py
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)
```
Raw data
{
"_id": null,
"home_page": "https://edugit.org/AlekSIS/libs/graphene-django-optimizer",
"name": "graphene-django-optimizer-reloaded",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "graphene django optimizer optimize graphql query prefetch select related",
"author": "Jonathan Weth",
"author_email": "dev@jonathanweth.de",
"download_url": "https://files.pythonhosted.org/packages/92/22/024fff630701948e52b3398f5bec9bfd62d442f601d18671891a2af0c994/graphene_django_optimizer_reloaded-0.9.2.tar.gz",
"platform": null,
"description": "# graphene-django-optimizer\n\nOptimize queries executed by [graphene-django](https://github.com/graphql-python/graphene-django) automatically, using [`select_related`](https://docs.djangoproject.com/en/2.0/ref/models/querysets/#select-related), [`prefetch_related`](https://docs.djangoproject.com/en/2.0/ref/models/querysets/#prefetch-related) and [`only`](https://docs.djangoproject.com/en/2.0/ref/models/querysets/#only) methods of Django QuerySet.\n\n\n\n## Install\n\n```bash\npip install graphene-django-optimizer\n```\n\n## Usage\n\nHaving the following schema based on [the tutorial of graphene-django](http://docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#hello-graphql-schema-and-object-types) (notice the use of `gql_optimizer`)\n\n```py\n# cookbook/ingredients/schema.py\nimport graphene\n\nfrom graphene_django.types import DjangoObjectType\nimport graphene_django_optimizer as gql_optimizer\n\nfrom cookbook.ingredients.models import Category, Ingredient\n\n\nclass CategoryType(DjangoObjectType):\n class Meta:\n model = Category\n\n\nclass IngredientType(DjangoObjectType):\n class Meta:\n model = Ingredient\n\n\nclass Query(graphene.ObjectType):\n all_categories = graphene.List(CategoryType)\n all_ingredients = graphene.List(IngredientType)\n\n def resolve_all_categories(root, info):\n return gql_optimizer.query(Category.objects.all(), info)\n\n def resolve_all_ingredients(root, info):\n return gql_optimizer.query(Ingredient.objects.all(), info)\n```\n\n\nWe will show some graphql queries and the queryset that will be executed.\n\nFetching all the ingredients with the related category:\n\n```graphql\n{\n allIngredients {\n id\n name\n category {\n id\n name\n }\n }\n}\n```\n\n```py\n# optimized queryset:\ningredients = (\n Ingredient.objects\n .select_related('category')\n .only('id', 'name', 'category__id', 'category__name')\n)\n```\n\nFetching all the categories with the related ingredients:\n\n```graphql\n{\n allCategories {\n id\n name\n ingredients {\n id\n name\n }\n }\n}\n```\n\n```py\n# optimized queryset:\ncategories = (\n Category.objects\n .only('id', 'name')\n .prefetch_related(Prefetch(\n 'ingredients',\n queryset=Ingredient.objects.only('id', 'name'),\n ))\n)\n```\n\n\n## Advanced usage\n\nSometimes we need to have a custom resolver function. In those cases, the field can't be auto optimized.\nSo we need to use `gql_optimizer.resolver_hints` decorator to indicate the optimizations.\n\nIf the resolver returns a model field, we can use the `model_field` argument:\n\n```py\nimport graphene\nimport graphene_django_optimizer as gql_optimizer\n\n\nclass ItemType(gql_optimizer.OptimizedDjangoObjectType):\n product = graphene.Field('ProductType')\n\n @gql_optimizer.resolver_hints(\n model_field='product',\n )\n def resolve_product(root, info):\n # check if user have permission for seeing the product\n if info.context.user.is_anonymous():\n return None\n return root.product\n```\n\nThis will automatically optimize any subfield of `product`.\n\nNow, if the resolver uses related fields, you can use the `select_related` argument:\n\n```py\nimport graphene\nimport graphene_django_optimizer as gql_optimizer\n\n\nclass ItemType(gql_optimizer.OptimizedDjangoObjectType):\n name = graphene.String()\n\n @gql_optimizer.resolver_hints(\n select_related=('product', 'shipping'),\n only=('product__name', 'shipping__name'),\n )\n def resolve_name(root, info):\n return '{} {}'.format(root.product.name, root.shipping.name)\n```\n\nNotice the usage of the type `OptimizedDjangoObjectType`, which enables\noptimization of any single node queries.\n\nFinally, if your field has an argument for filtering results,\nyou can use the `prefetch_related` argument with a function\nthat returns a `Prefetch` instance as the value.\n\n```py\nfrom django.db.models import Prefetch\nimport graphene\nimport graphene_django_optimizer as gql_optimizer\n\n\nclass CartType(gql_optimizer.OptimizedDjangoObjectType):\n items = graphene.List(\n 'ItemType',\n product_id=graphene.ID(),\n )\n\n @gql_optimizer.resolver_hints(\n prefetch_related=lambda info, product_id: Prefetch(\n 'items',\n queryset=gql_optimizer.query(Item.objects.filter(product_id=product_id), info),\n to_attr='gql_product_id_' + product_id,\n ),\n )\n def resolve_items(root, info, product_id):\n return getattr(root, 'gql_product_id_' + product_id)\n```\n\nWith these hints, any field can be optimized.\n\n\n### Optimize with non model fields\n\nSometimes we need to have a custom non model fields. In those cases, the optimizer would not optimize with the Django `.only()` method.\nSo if we still want to optimize with the `.only()` method, we need to use `disable_abort_only` option:\n\n```py\n\nclass IngredientType(gql_optimizer.OptimizedDjangoObjectType):\n calculated_calories = graphene.String()\n\n class Meta:\n model = Ingredient\n\n def resolve_calculated_calories(root, info):\n return get_calories_for_ingredient(root.id)\n\n\nclass Query(object):\n all_ingredients = graphene.List(IngredientType)\n\n def resolve_all_ingredients(root, info):\n return gql_optimizer.query(Ingredient.objects.all(), info, disable_abort_only=True)\n```\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Optimize database access inside graphene queries.",
"version": "0.9.2",
"project_urls": {
"Homepage": "https://edugit.org/AlekSIS/libs/graphene-django-optimizer"
},
"split_keywords": [
"graphene",
"django",
"optimizer",
"optimize",
"graphql",
"query",
"prefetch",
"select",
"related"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2395e6fe3ea2750a29aea67386249e5cffee57a3a105aa40059a88638e25dd67",
"md5": "06fa088f0efc2cf9a9a5ebef0956f801",
"sha256": "10b9522487131b56ceeceda5379fb872341109f1a63b7186b3519b4b7a66620b"
},
"downloads": -1,
"filename": "graphene_django_optimizer_reloaded-0.9.2-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "06fa088f0efc2cf9a9a5ebef0956f801",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 10177,
"upload_time": "2024-12-01T20:53:35",
"upload_time_iso_8601": "2024-12-01T20:53:35.559134Z",
"url": "https://files.pythonhosted.org/packages/23/95/e6fe3ea2750a29aea67386249e5cffee57a3a105aa40059a88638e25dd67/graphene_django_optimizer_reloaded-0.9.2-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "9222024fff630701948e52b3398f5bec9bfd62d442f601d18671891a2af0c994",
"md5": "e0095762a801a401db2e2294402b9183",
"sha256": "488eb4a6a6ece6935027da4149c7a328c6dfc58ca40af7d4c00daa33884309a2"
},
"downloads": -1,
"filename": "graphene_django_optimizer_reloaded-0.9.2.tar.gz",
"has_sig": false,
"md5_digest": "e0095762a801a401db2e2294402b9183",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 14164,
"upload_time": "2024-12-01T20:53:37",
"upload_time_iso_8601": "2024-12-01T20:53:37.482181Z",
"url": "https://files.pythonhosted.org/packages/92/22/024fff630701948e52b3398f5bec9bfd62d442f601d18671891a2af0c994/graphene_django_optimizer_reloaded-0.9.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-01 20:53:37",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "graphene-django-optimizer-reloaded"
}