graphene-django-optimizer


Namegraphene-django-optimizer JSON
Version 0.10.0 PyPI version JSON
download
home_pagehttps://github.com/tfoxy/graphene-django-optimizer
SummaryOptimize database access inside graphene queries.
upload_time2023-08-05 17:53:27
maintainer
docs_urlNone
authorTomás Fox
requires_python
licenseMIT
keywords graphene django optimizer optimize graphql query prefetch select related
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI
coveralls test coverage
            # graphene-django-optimizer

[![build status](https://img.shields.io/travis/tfoxy/graphene-django-optimizer.svg)](https://travis-ci.com/github/tfoxy/graphene-django-optimizer)
[![coverage](https://img.shields.io/codecov/c/github/tfoxy/graphene-django-optimizer.svg)](https://codecov.io/gh/tfoxy/graphene-django-optimizer)
[![PyPI version](https://img.shields.io/pypi/v/graphene-django-optimizer.svg)](https://pypi.org/project/graphene-django-optimizer/)
![python version](https://img.shields.io/pypi/pyversions/graphene-django-optimizer.svg)
![django version](https://img.shields.io/pypi/djversions/graphene-django-optimizer.svg)

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
```

_Note: If you are using Graphene V2, please install version `0.8`. v0.9 and forward will support only Graphene V3_

## 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)
```

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/tfoxy/graphene-django-optimizer",
    "name": "graphene-django-optimizer",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "graphene django optimizer optimize graphql query prefetch select related",
    "author": "Tom\u00e1s Fox",
    "author_email": "tomas.c.fox@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/fa/6c/cd89093083debac1c8eba62f2f1daa55caeb3a27f0f908761a7e68e3ed2e/graphene-django-optimizer-0.10.0.tar.gz",
    "platform": null,
    "description": "# graphene-django-optimizer\n\n[![build status](https://img.shields.io/travis/tfoxy/graphene-django-optimizer.svg)](https://travis-ci.com/github/tfoxy/graphene-django-optimizer)\n[![coverage](https://img.shields.io/codecov/c/github/tfoxy/graphene-django-optimizer.svg)](https://codecov.io/gh/tfoxy/graphene-django-optimizer)\n[![PyPI version](https://img.shields.io/pypi/v/graphene-django-optimizer.svg)](https://pypi.org/project/graphene-django-optimizer/)\n![python version](https://img.shields.io/pypi/pyversions/graphene-django-optimizer.svg)\n![django version](https://img.shields.io/pypi/djversions/graphene-django-optimizer.svg)\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## Install\n\n```bash\npip install graphene-django-optimizer\n```\n\n_Note: If you are using Graphene V2, please install version `0.8`. v0.9 and forward will support only Graphene V3_\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\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## 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### 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\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Optimize database access inside graphene queries.",
    "version": "0.10.0",
    "project_urls": {
        "Homepage": "https://github.com/tfoxy/graphene-django-optimizer"
    },
    "split_keywords": [
        "graphene",
        "django",
        "optimizer",
        "optimize",
        "graphql",
        "query",
        "prefetch",
        "select",
        "related"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1be32f0d01168d9c781d17c8f49a8ffe3d52a370382b9a5f4f4a756d86a46a01",
                "md5": "cb4a31d2932367a50e77fefe26d6a162",
                "sha256": "5b81aaa114514c9a89d064aaaf09c8e00c27d1cf6f734993f0ea5afe776041b9"
            },
            "downloads": -1,
            "filename": "graphene_django_optimizer-0.10.0-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "cb4a31d2932367a50e77fefe26d6a162",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": null,
            "size": 10442,
            "upload_time": "2023-08-05T17:53:25",
            "upload_time_iso_8601": "2023-08-05T17:53:25.936067Z",
            "url": "https://files.pythonhosted.org/packages/1b/e3/2f0d01168d9c781d17c8f49a8ffe3d52a370382b9a5f4f4a756d86a46a01/graphene_django_optimizer-0.10.0-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fa6ccd89093083debac1c8eba62f2f1daa55caeb3a27f0f908761a7e68e3ed2e",
                "md5": "b50fae4fe57ec4c4a6b658aae27e04b4",
                "sha256": "5a5d9d1e1abda3571bdff77a71af5e71b994e5e9d97d17a576df89181284075d"
            },
            "downloads": -1,
            "filename": "graphene-django-optimizer-0.10.0.tar.gz",
            "has_sig": false,
            "md5_digest": "b50fae4fe57ec4c4a6b658aae27e04b4",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 14681,
            "upload_time": "2023-08-05T17:53:27",
            "upload_time_iso_8601": "2023-08-05T17:53:27.486239Z",
            "url": "https://files.pythonhosted.org/packages/fa/6c/cd89093083debac1c8eba62f2f1daa55caeb3a27f0f908761a7e68e3ed2e/graphene-django-optimizer-0.10.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-08-05 17:53:27",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "tfoxy",
    "github_project": "graphene-django-optimizer",
    "travis_ci": true,
    "coveralls": true,
    "github_actions": false,
    "requirements": [],
    "lcname": "graphene-django-optimizer"
}
        
Elapsed time: 2.39221s