Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
graphene-django query cost analysis / complexity limits
from django.conf.urls import url
from django.contrib import admin
from graphene_django.views import GraphQLView
from django.views.decorators.csrf import csrf_exempt
from graphql.backend.core import GraphQLCoreBackend
"""
This piece of code implements complexity limits for a graphene-django API.
You can limit:
- Query size in characters
- Query width
- Number of requested fields
- Query depth
- Overall cost (see the calculation below)
The code is based on jkimbo's answer on a GitHub issue:
https://github.com/graphql-python/graphene/issues/772#issuecomment-400094405
"""
class CostConfig:
# To disable a limit, put an arbitrary large value like 10**10
max_query_size = 3000 # max query size in characters
max_width = 10 # max number of subfields of one field
max_depth = 3 # max
max_fields = 20
max_cost = 100 # max overall cost
# The calculation is done this way :
# cost(field) = default_field_cost + depth_cost_multiplier * cost(subfields)
default_field_cost = 1
depth_cost_multiplier = 3
def measure_complexity(selection_set, level=1):
max_depth = level
cost = CostConfig.default_field_cost
width = 0
widths = []
fields = 0
for field in selection_set.selections:
cost += CostConfig.default_field_cost
fields += 1
if field.selection_set:
width += 1
new_depth, field_cost, field_width, fields = measure_complexity(field.selection_set, level=level + 1)
widths.append(field_width)
cost += CostConfig.depth_cost_multiplier * field_cost
if new_depth > max_depth:
max_depth = new_depth
widths.append(width)
max_width = max(widths)
return max_depth, cost, max_width, fields
class DepthAnalysisBackend(GraphQLCoreBackend):
def document_from_string(self, schema, document_string):
if len(document_string) > CostConfig.max_query_size:
raise Exception('Query is too long')
document = super().document_from_string(schema, document_string)
ast = document.document_ast
for definition in ast.definitions:
# We are only interested in queries
if definition.operation != 'query':
continue
depth, cost, width, fields = measure_complexity(definition.selection_set)
print(cost, depth, width, fields)
if depth > CostConfig.max_depth:
raise Exception('Query is too deep')
if cost > CostConfig.max_cost:
raise Exception('Query is too complex')
if width > CostConfig.max_width:
raise Exception('Query is too wide')
if fields > CostConfig.max_fields:
raise Exception('Query has too many fields')
return document
urlpatterns = [
...
url(r"^graphql/", csrf_exempt(GraphQLView.as_view(graphiql=True, backend=DepthAnalysisBackend()))),
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment