mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-25 02:54:06 +03:00
enhancement: DjangoDebugContext captures exceptions and allows captured stack traces to be queried (#1122)
This commit is contained in:
parent
6046a710c8
commit
e9f25ecf2d
|
@ -4,7 +4,7 @@ Django Debug Middleware
|
||||||
You can debug your GraphQL queries in a similar way to
|
You can debug your GraphQL queries in a similar way to
|
||||||
`django-debug-toolbar <https://django-debug-toolbar.readthedocs.org/>`__,
|
`django-debug-toolbar <https://django-debug-toolbar.readthedocs.org/>`__,
|
||||||
but outputting in the results in GraphQL response as fields, instead of
|
but outputting in the results in GraphQL response as fields, instead of
|
||||||
the graphical HTML interface.
|
the graphical HTML interface. Exceptions with their stack traces are also exposed.
|
||||||
|
|
||||||
For that, you will need to add the plugin in your graphene schema.
|
For that, you will need to add the plugin in your graphene schema.
|
||||||
|
|
||||||
|
@ -63,6 +63,10 @@ the GraphQL request, like:
|
||||||
sql {
|
sql {
|
||||||
rawSql
|
rawSql
|
||||||
}
|
}
|
||||||
|
exceptions {
|
||||||
|
message
|
||||||
|
stack
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
0
graphene_django/debug/exception/__init__.py
Normal file
0
graphene_django/debug/exception/__init__.py
Normal file
17
graphene_django/debug/exception/formating.py
Normal file
17
graphene_django/debug/exception/formating.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
from .types import DjangoDebugException
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_exception(exception):
|
||||||
|
return DjangoDebugException(
|
||||||
|
message=force_str(exception),
|
||||||
|
exc_type=force_str(type(exception)),
|
||||||
|
stack="".join(
|
||||||
|
traceback.format_exception(
|
||||||
|
etype=type(exception), value=exception, tb=exception.__traceback__
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
10
graphene_django/debug/exception/types.py
Normal file
10
graphene_django/debug/exception/types.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from graphene import ObjectType, String
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoDebugException(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
description = "Represents a single exception raised."
|
||||||
|
|
||||||
|
exc_type = String(required=True, description="The class of the exception")
|
||||||
|
message = String(required=True, description="The message of the exception")
|
||||||
|
stack = String(required=True, description="The stack trace")
|
|
@ -3,6 +3,7 @@ from django.db import connections
|
||||||
from promise import Promise
|
from promise import Promise
|
||||||
|
|
||||||
from .sql.tracking import unwrap_cursor, wrap_cursor
|
from .sql.tracking import unwrap_cursor, wrap_cursor
|
||||||
|
from .exception.formating import wrap_exception
|
||||||
from .types import DjangoDebug
|
from .types import DjangoDebug
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,8 +11,8 @@ class DjangoDebugContext(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.debug_promise = None
|
self.debug_promise = None
|
||||||
self.promises = []
|
self.promises = []
|
||||||
|
self.object = DjangoDebug(sql=[], exceptions=[])
|
||||||
self.enable_instrumentation()
|
self.enable_instrumentation()
|
||||||
self.object = DjangoDebug(sql=[])
|
|
||||||
|
|
||||||
def get_debug_promise(self):
|
def get_debug_promise(self):
|
||||||
if not self.debug_promise:
|
if not self.debug_promise:
|
||||||
|
@ -19,6 +20,11 @@ class DjangoDebugContext(object):
|
||||||
self.promises = []
|
self.promises = []
|
||||||
return self.debug_promise.then(self.on_resolve_all_promises).get()
|
return self.debug_promise.then(self.on_resolve_all_promises).get()
|
||||||
|
|
||||||
|
def on_resolve_error(self, value):
|
||||||
|
if hasattr(self, "object"):
|
||||||
|
self.object.exceptions.append(wrap_exception(value))
|
||||||
|
return Promise.reject(value)
|
||||||
|
|
||||||
def on_resolve_all_promises(self, values):
|
def on_resolve_all_promises(self, values):
|
||||||
if self.promises:
|
if self.promises:
|
||||||
self.debug_promise = None
|
self.debug_promise = None
|
||||||
|
@ -57,6 +63,9 @@ class DjangoDebugMiddleware(object):
|
||||||
)
|
)
|
||||||
if info.schema.get_type("DjangoDebug") == info.return_type:
|
if info.schema.get_type("DjangoDebug") == info.return_type:
|
||||||
return context.django_debug.get_debug_promise()
|
return context.django_debug.get_debug_promise()
|
||||||
|
try:
|
||||||
promise = next(root, info, **args)
|
promise = next(root, info, **args)
|
||||||
|
except Exception as e:
|
||||||
|
return context.django_debug.on_resolve_error(e)
|
||||||
context.django_debug.add_promise(promise)
|
context.django_debug.add_promise(promise)
|
||||||
return promise
|
return promise
|
||||||
|
|
|
@ -272,3 +272,42 @@ def test_should_query_connectionfilter(graphene_settings, max_limit):
|
||||||
assert "COUNT" in result.data["_debug"]["sql"][0]["rawSql"]
|
assert "COUNT" in result.data["_debug"]["sql"][0]["rawSql"]
|
||||||
query = str(Reporter.objects.all()[:1].query)
|
query = str(Reporter.objects.all()[:1].query)
|
||||||
assert result.data["_debug"]["sql"][1]["rawSql"] == query
|
assert result.data["_debug"]["sql"][1]["rawSql"] == query
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_query_stack_trace():
|
||||||
|
class ReporterType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Reporter
|
||||||
|
interfaces = (Node,)
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
reporter = graphene.Field(ReporterType)
|
||||||
|
debug = graphene.Field(DjangoDebug, name="_debug")
|
||||||
|
|
||||||
|
def resolve_reporter(self, info, **args):
|
||||||
|
raise Exception("caught stack trace")
|
||||||
|
|
||||||
|
query = """
|
||||||
|
query ReporterQuery {
|
||||||
|
reporter {
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
_debug {
|
||||||
|
exceptions {
|
||||||
|
message
|
||||||
|
stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
schema = graphene.Schema(query=Query)
|
||||||
|
result = schema.execute(
|
||||||
|
query, context_value=context(), middleware=[DjangoDebugMiddleware()]
|
||||||
|
)
|
||||||
|
assert result.errors
|
||||||
|
assert len(result.data["_debug"]["exceptions"])
|
||||||
|
debug_exception = result.data["_debug"]["exceptions"][0]
|
||||||
|
assert debug_exception["stack"].count("\n") > 1
|
||||||
|
assert "test_query.py" in debug_exception["stack"]
|
||||||
|
assert debug_exception["message"] == "caught stack trace"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from graphene import List, ObjectType
|
from graphene import List, ObjectType
|
||||||
|
|
||||||
from .sql.types import DjangoDebugSQL
|
from .sql.types import DjangoDebugSQL
|
||||||
|
from .exception.types import DjangoDebugException
|
||||||
|
|
||||||
|
|
||||||
class DjangoDebug(ObjectType):
|
class DjangoDebug(ObjectType):
|
||||||
|
@ -8,3 +9,6 @@ class DjangoDebug(ObjectType):
|
||||||
description = "Debugging information for the current query."
|
description = "Debugging information for the current query."
|
||||||
|
|
||||||
sql = List(DjangoDebugSQL, description="Executed SQL queries for this API query.")
|
sql = List(DjangoDebugSQL, description="Executed SQL queries for this API query.")
|
||||||
|
exceptions = List(
|
||||||
|
DjangoDebugException, description="Raise exceptions for this API query."
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user