Graphene-Django Optimizer
graphene-django-optimizer is a library for Graphene-Django that automatically optimizes database access (e.g., using `select_related`, `prefetch_related`) within GraphQL queries to mitigate N+1 query problems. Its current version is 0.10.0, and it follows a release cadence driven by new Graphene or Django versions and bug fixes, with a focus on supporting the latest major versions of its dependencies.
Common errors
-
TypeError: __init__() got an unexpected keyword argument 'extensions'
cause Attempting to pass `extensions` argument to `graphene.Schema` constructor when using Graphene 2 with `graphene-django-optimizer >= 0.9.0` (which expects Graphene 3).fixIf using Graphene 2, pin `graphene-django-optimizer` to `<0.9.0`. If you intend to use Graphene 3, ensure your Graphene installation is `graphene >= 3.0`. -
AttributeError: 'Manager' object has no attribute 'select_related' or 'prefetch_related' (or similar N+1 query warning)
cause The `DjangoOptimizerExtension` is not correctly configured in `settings.GRAPHENE['EXTENSIONS']`, or `QueryOptimizer.optimize_query` is not called for custom resolvers.fixVerify `DjangoOptimizerExtension` is present in `settings.GRAPHENE['EXTENSIONS']`. For custom resolvers, ensure `QueryOptimizer.optimize_query(queryset, info)` is called with the base queryset before returning, or use the `@optimize` decorator. -
No module named 'graphene_django_optimizer'
cause The `graphene-django-optimizer` package is not installed or the Python environment is incorrect.fixRun `pip install graphene-django-optimizer` in your project's virtual environment. Ensure your IDE or server is using the correct Python interpreter. -
TypeError: Object of type Book is not JSON serializable (when debugging/printing query result)
cause This error isn't directly from `graphene-django-optimizer`, but often occurs when trying to debug optimized queries that return Django QuerySet objects directly without proper serialization or when running Graphene outside a Django request context.fixEnsure the GraphQL schema execution correctly serializes the output. In a Django project, this typically happens automatically within `graphene_django.views.GraphQLView`. When testing, use `schema.execute(query).data` to get the dict representation.
Warnings
- breaking Version 0.9.0 introduced support for Graphene 3, simultaneously dropping support for Graphene 2. If you are using Graphene 2, you must pin your `graphene-django-optimizer` version to `<0.9.0` (e.g., use `~=0.8.0`).
- gotcha Version 0.10.0 fixed a compatibility issue with `graphql-core v3.2`. If you are using `graphql-core >= 3.2` with `graphene-django-optimizer < 0.10.0`, you might encounter runtime errors or unexpected behavior.
- breaking Versions prior to 0.6.0 supported older Django and Python versions. Current versions (>=0.6.0) require Django `>=2.2` and Python `>=3.6`.
- gotcha Automatic optimization through `DjangoOptimizerExtension` only applies to fields resolved by `DjangoObjectType`'s default resolver. For custom resolvers, you must manually apply `QueryOptimizer.optimize_query(queryset, info)` or use the `@optimize` decorator.
Install
-
pip install graphene-django-optimizer
Imports
- DjangoOptimizerExtension
from graphene_django_optimizer import DjangoOptimizerExtension
- QueryOptimizer
from graphene_django_optimizer import QueryOptimizer
- optimize
from graphene_django_optimizer.resolver import optimize
from graphene_django_optimizer import optimize
Quickstart
import graphene
from django.db import models
from django.conf import settings
from graphene_django.types import DjangoObjectType
from graphene_django_optimizer import DjangoOptimizerExtension
# Minimal Django setup required for DjangoObjectType to function
# In a real Django project, these settings would be in your settings.py
if not settings.configured:
settings.configure(
INSTALLED_APPS=['graphene_django', 'graphene_django_optimizer', 'myapp'],
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},
GRAPHENE={'EXTENSIONS': [DjangoOptimizerExtension]}, # Enable globally via settings
# Needed for Django models to be discovered
MIGRATION_MODULES={'myapp': None}
)
import django
django.setup()
# Define a simple Django model
class Author(models.Model):
name = models.CharField(max_length=100)
class Meta:
app_label = 'myapp' # Critical for in-memory Django setup
class Book(models.Model):
title = models.CharField(max_length=200)
author_obj = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
class Meta:
app_label = 'myapp'
# Define Graphene types for the models
class AuthorType(DjangoObjectType):
class Meta:
model = Author
fields = ("id", "name", "books")
class BookType(DjangoObjectType):
class Meta:
model = Book
fields = ("id", "title", "author_obj")
# Define a Graphene Query
class Query(graphene.ObjectType):
all_authors = graphene.List(AuthorType)
all_books = graphene.List(BookType)
def resolve_all_authors(root, info):
# The DjangoOptimizerExtension will automatically apply select_related/prefetch_related
# for related fields requested in the GraphQL query (e.g., 'books' on AuthorType)
return Author.objects.all()
def resolve_all_books(root, info):
return Book.objects.all()
# Create the Graphene Schema
# The extension is enabled via settings.GRAPHENE['EXTENSIONS']
schema = graphene.Schema(query=Query)
# --- Example Usage (for demonstration, not typically part of quickstart.code) ---
# Create some dummy data
author1 = Author.objects.create(name="Douglas Adams")
author2 = Author.objects.create(name="Jane Austen")
Book.objects.create(title="The Hitchhiker's Guide to the Galaxy", author_obj=author1)
Book.objects.create(title="Pride and Prejudice", author_obj=author2)
Book.objects.create(title="The Restaurant at the End of the Universe", author_obj=author1)
# Define a GraphQL query that would typically cause N+1 without optimization
query = """
query {
allBooks {
id
title
author_obj {
name
}
}
}
"""
# Execute the query
result = schema.execute(query)
# Check for errors and data (in a real scenario, you'd inspect logs for N+1)
assert not result.errors
assert len(result.data['allBooks']) == 3
assert result.data['allBooks'][0]['author_obj']['name'] == 'Douglas Adams'
print("Optimization enabled successfully! Check your database query logs if available.")