mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-21 17:16:56 +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
|
||||
`django-debug-toolbar <https://django-debug-toolbar.readthedocs.org/>`__,
|
||||
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.
|
||||
|
||||
|
@ -63,6 +63,10 @@ the GraphQL request, like:
|
|||
sql {
|
||||
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 .sql.tracking import unwrap_cursor, wrap_cursor
|
||||
from .exception.formating import wrap_exception
|
||||
from .types import DjangoDebug
|
||||
|
||||
|
||||
|
@ -10,8 +11,8 @@ class DjangoDebugContext(object):
|
|||
def __init__(self):
|
||||
self.debug_promise = None
|
||||
self.promises = []
|
||||
self.object = DjangoDebug(sql=[], exceptions=[])
|
||||
self.enable_instrumentation()
|
||||
self.object = DjangoDebug(sql=[])
|
||||
|
||||
def get_debug_promise(self):
|
||||
if not self.debug_promise:
|
||||
|
@ -19,6 +20,11 @@ class DjangoDebugContext(object):
|
|||
self.promises = []
|
||||
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):
|
||||
if self.promises:
|
||||
self.debug_promise = None
|
||||
|
@ -57,6 +63,9 @@ class DjangoDebugMiddleware(object):
|
|||
)
|
||||
if info.schema.get_type("DjangoDebug") == info.return_type:
|
||||
return context.django_debug.get_debug_promise()
|
||||
promise = next(root, info, **args)
|
||||
try:
|
||||
promise = next(root, info, **args)
|
||||
except Exception as e:
|
||||
return context.django_debug.on_resolve_error(e)
|
||||
context.django_debug.add_promise(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"]
|
||||
query = str(Reporter.objects.all()[:1].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 .sql.types import DjangoDebugSQL
|
||||
from .exception.types import DjangoDebugException
|
||||
|
||||
|
||||
class DjangoDebug(ObjectType):
|
||||
|
@ -8,3 +9,6 @@ class DjangoDebug(ObjectType):
|
|||
description = "Debugging information for the current 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