Django integration for Graphene enabling GraphQL APIs in Django applications
—
Development debugging tools for GraphQL queries with SQL query inspection, performance monitoring, and comprehensive middleware integration. Provides detailed insights into GraphQL execution and database query patterns.
Middleware to capture SQL queries and timing information during GraphQL execution with detailed performance metrics.
class DjangoDebugMiddleware:
"""
Middleware to capture SQL queries and timing information.
Captures all SQL queries executed during GraphQL field resolution
with timing, parameters, and performance analysis for development
and debugging purposes.
"""
def __init__(self):
"""Initialize debug middleware with query tracking."""
def resolve(self, next, root, info, **args):
"""
Middleware resolver function with SQL tracking.
Parameters:
- next: Next resolver in chain
- root: GraphQL root object
- info: GraphQL execution info
- **args: Resolver arguments
Returns:
Resolver result with debug information attached
"""Debug information container for GraphQL queries with comprehensive SQL query details and execution metrics.
class DjangoDebug(graphene.ObjectType):
"""
Debug information container for GraphQL queries.
Provides structured access to SQL queries, timing information,
and performance metrics captured during GraphQL execution.
"""
sql = graphene.List(DjangoDebugSQL)
def resolve_sql(self, info):
"""
Resolve SQL queries from debug context.
Parameters:
- info: GraphQL execution info
Returns:
List[DjangoDebugSQL]: List of executed SQL queries
"""Individual SQL query debug information with comprehensive query analysis and performance metrics.
class DjangoDebugSQL(graphene.ObjectType):
"""
Individual SQL query debug information.
Detailed information about each SQL query executed during
GraphQL resolution including timing, parameters, and metadata.
"""
vendor = graphene.String() # Database vendor (postgresql, mysql, etc.)
alias = graphene.String() # Database alias/connection name
sql = graphene.String() # Formatted SQL query
duration = graphene.Float() # Query execution time in seconds
raw_sql = graphene.String() # Raw SQL with parameter placeholders
params = graphene.String() # Query parameters as JSON string
start_time = graphene.Float() # Query start timestamp
stop_time = graphene.Float() # Query completion timestamp
is_slow = graphene.Boolean() # Whether query exceeds slow threshold
is_select = graphene.Boolean() # Whether query is SELECT statement
trans_id = graphene.String() # Transaction ID
trans_status = graphene.String() # Transaction status
iso_level = graphene.String() # Postgres isolation level if available
encoding = graphene.String() # Database encoding
def resolve_duration(self, info):
"""
Calculate query duration in seconds.
Returns:
float: Duration in seconds
"""
def resolve_is_slow(self, info):
"""
Determine if query exceeds slow query threshold.
Returns:
bool: True if query is considered slow
"""
def resolve_is_select(self, info):
"""
Determine if query is a SELECT statement.
Returns:
bool: True if query is SELECT
"""
def resolve_params(self, info):
"""
Format query parameters as JSON string.
Returns:
str: JSON-formatted parameters
"""from graphene_django.debug import DjangoDebugMiddleware, DjangoDebug
import graphene
class Query(graphene.ObjectType):
debug = graphene.Field(DjangoDebug)
users = graphene.List(UserType)
def resolve_users(self, info):
return User.objects.all()
schema = graphene.Schema(
query=Query,
middleware=[DjangoDebugMiddleware()]
)
# GraphQL query with debug info:
# query {
# users {
# id
# username
# }
# debug {
# sql {
# sql
# duration
# vendor
# }
# }
# }from django.conf import settings
from graphene_django.views import GraphQLView
from graphene_django.debug import DjangoDebugMiddleware
def create_graphql_view():
middleware = []
if settings.DEBUG:
middleware.append(DjangoDebugMiddleware())
return GraphQLView.as_view(
schema=schema,
middleware=middleware,
graphiql=settings.DEBUG
)
# urls.py
urlpatterns = [
path('graphql/', create_graphql_view()),
]class DebugQuery(graphene.ObjectType):
debug = graphene.Field(DjangoDebug)
def resolve_debug(self, info):
# Custom debug information
return DjangoDebug()
class Query(DebugQuery, graphene.ObjectType):
users = graphene.List(UserType)
posts = graphene.List(PostType)
def resolve_users(self, info):
# This will be tracked by debug middleware
return User.objects.select_related('profile').all()
def resolve_posts(self, info):
# Complex query for debugging
return Post.objects.select_related('author').prefetch_related('tags').all()
schema = graphene.Schema(
query=Query,
middleware=[DjangoDebugMiddleware()]
)from django.conf import settings
class ConditionalDebugMiddleware:
def __init__(self):
self.debug_middleware = DjangoDebugMiddleware() if settings.DEBUG else None
def resolve(self, next, root, info, **args):
if self.debug_middleware:
return self.debug_middleware.resolve(next, root, info, **args)
return next(root, info, **args)
class Query(graphene.ObjectType):
debug = graphene.Field(DjangoDebug)
users = graphene.List(UserType)
def resolve_debug(self, info):
if not settings.DEBUG:
raise Exception("Debug information not available in production")
return DjangoDebug()
schema = graphene.Schema(
query=Query,
middleware=[ConditionalDebugMiddleware()]
)class PerformanceQuery(graphene.ObjectType):
debug = graphene.Field(DjangoDebug)
users = graphene.List(UserType)
def resolve_users(self, info):
# Intentionally inefficient query for debugging
users = []
for user in User.objects.all():
# This creates N+1 query problem
user.post_count = user.post_set.count()
users.append(user)
return users
# GraphQL query to analyze performance:
# query {
# users {
# id
# username
# }
# debug {
# sql {
# sql
# duration
# isSelect
# isSlow
# }
# }
# }import time
import logging
from graphene_django.debug import DjangoDebugMiddleware
logger = logging.getLogger(__name__)
class CustomDebugMiddleware(DjangoDebugMiddleware):
def resolve(self, next, root, info, **args):
start_time = time.time()
# Call parent middleware
result = super().resolve(next, root, info, **args)
end_time = time.time()
duration = end_time - start_time
# Log slow resolvers
if duration > 0.1: # 100ms threshold
logger.warning(
f"Slow resolver: {info.field_name} took {duration:.3f}s"
)
# Add custom debug info
if hasattr(info.context, 'debug'):
info.context.debug['resolvers'] = getattr(
info.context.debug, 'resolvers', []
) + [{
'field': info.field_name,
'duration': duration,
'args': args
}]
return result
class ExtendedDebug(DjangoDebug):
resolvers = graphene.List(graphene.JSONString)
def resolve_resolvers(self, info):
return getattr(info.context.debug, 'resolvers', [])
class Query(graphene.ObjectType):
debug = graphene.Field(ExtendedDebug)
users = graphene.List(UserType)
schema = graphene.Schema(
query=Query,
middleware=[CustomDebugMiddleware()]
)from django.core.exceptions import PermissionDenied
class SecureDebugMiddleware:
def __init__(self):
self.debug_middleware = DjangoDebugMiddleware()
def resolve(self, next, root, info, **args):
# Only enable debug for staff users
if not getattr(info.context.user, 'is_staff', False):
return next(root, info, **args)
return self.debug_middleware.resolve(next, root, info, **args)
class SecureDebugQuery(graphene.ObjectType):
debug = graphene.Field(DjangoDebug)
def resolve_debug(self, info):
if not getattr(info.context.user, 'is_staff', False):
raise PermissionDenied("Debug information requires staff access")
return DjangoDebug()
class Query(SecureDebugQuery, graphene.ObjectType):
users = graphene.List(UserType)
schema = graphene.Schema(
query=Query,
middleware=[SecureDebugMiddleware()]
)# For GraphiQL development interface with debug info
from graphene_django.views import GraphQLView
class DebugGraphQLView(GraphQLView):
def get_context(self, request):
context = super().get_context(request)
# Initialize debug context
if settings.DEBUG:
context['debug'] = {}
return context
urlpatterns = [
path('graphql/', DebugGraphQLView.as_view(
schema=schema,
middleware=[DjangoDebugMiddleware()],
graphiql=settings.DEBUG
)),
]
# In GraphiQL, you can now query:
# {
# users { id username }
# debug {
# sql {
# sql
# duration
# params
# }
# }
# }class DatabaseDebugQuery(graphene.ObjectType):
debug = graphene.Field(DjangoDebug)
connection_info = graphene.Field(graphene.JSONString)
def resolve_connection_info(self, info):
if not settings.DEBUG:
return None
from django.db import connections
connection_data = {}
for alias in connections:
conn = connections[alias]
connection_data[alias] = {
'vendor': conn.vendor,
'settings': {
'ENGINE': conn.settings_dict.get('ENGINE'),
'NAME': conn.settings_dict.get('NAME'),
'HOST': conn.settings_dict.get('HOST'),
'PORT': conn.settings_dict.get('PORT'),
}
}
return connection_data
# Query database connection info:
# query {
# connectionInfo
# debug {
# sql {
# alias
# vendor
# sql
# }
# }
# }Install with Tessl CLI
npx tessl i tessl/pypi-graphene-django