Skip to content

Instantly share code, notes, and snippets.

@jphalip
Created February 19, 2014 21:23
Show Gist options
  • Save jphalip/9101900 to your computer and use it in GitHub Desktop.
Save jphalip/9101900 to your computer and use it in GitHub Desktop.
Suggested patch for restless
diff --git a/restless/dj.py b/restless/dj.py
index 9af449d..d963699 100644
--- a/restless/dj.py
+++ b/restless/dj.py
@@ -6,7 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
-from .exceptions import NotFound
+from .exceptions import NotFound, Unauthorized
from .resources import Resource
@@ -36,12 +36,15 @@ class DjangoResource(Resource):
resp.status_code = status
return resp
- def build_error(self, err):
- # A bit nicer behavior surrounding things that don't exist.
+ def build_error(self, err, traceback=None):
if isinstance(err, ObjectDoesNotExist):
+ # A bit nicer behavior surrounding things that don't exist.
err = NotFound(msg=six.text_type(err))
-
- return super(DjangoResource, self).build_error(err)
+ elif isinstance(err, Unauthorized):
+ pass
+ elif self.is_debug():
+ raise
+ return super(DjangoResource, self).build_error(err, traceback)
@classmethod
def build_url_name(cls, name, name_prefix=None):
diff --git a/restless/fl.py b/restless/fl.py
index 80dad3e..75106ca 100644
--- a/restless/fl.py
+++ b/restless/fl.py
@@ -44,6 +44,11 @@ class FlaskResource(Resource):
from flask import current_app
return current_app.debug
+ def build_error(self, err, traceback=None):
+ if self.is_debug():
+ raise
+ return super(FlaskResource, self).build_error(err, traceback)
+
def build_response(self, data, status=200):
return make_response(data, status, {
'Content-Type': 'application/json'
diff --git a/restless/it.py b/restless/it.py
index 277c17b..ade5b5b 100644
--- a/restless/it.py
+++ b/restless/it.py
@@ -16,6 +16,11 @@ class IttyResource(Resource):
def is_debug(self):
return self.debug
+ def build_error(self, err, traceback=None):
+ if self.is_debug():
+ raise
+ return super(IttyResource, self).build_error(err, traceback)
+
def build_response(self, data, status=200):
return itty.Response(data, status=status, content_type='application/json')
diff --git a/restless/pyr.py b/restless/pyr.py
index 2d6d162..abde721 100644
--- a/restless/pyr.py
+++ b/restless/pyr.py
@@ -26,6 +26,11 @@ class PyramidResource(Resource):
return _wrapper
+ def build_error(self, err, traceback=None):
+ if self.is_debug():
+ raise
+ return super(PyramidResource, self).build_error(err, traceback)
+
def build_response(self, data, status=200):
resp = Response(data, status_code=status, content_type="application/json")
return resp
diff --git a/restless/resources.py b/restless/resources.py
index b097f6e..60d688a 100644
--- a/restless/resources.py
+++ b/restless/resources.py
@@ -1,8 +1,9 @@
import six
+import sys
from .constants import OK, CREATED, ACCEPTED, NO_CONTENT
from .exceptions import MethodNotImplemented, Unauthorized
-from .utils import json, lookup_data, MoreTypesJSONEncoder
+from .utils import json, lookup_data, MoreTypesJSONEncoder, format_traceback
class Resource(object):
@@ -181,7 +182,7 @@ class Resource(object):
resp.status_code = status
return resp
- def build_error(self, err):
+ def build_error(self, err, traceback=None):
"""
When an exception is encountered, this generates a JSON error message
for display to the user.
@@ -190,11 +191,17 @@ class Resource(object):
beware of sensitive data leaking.
:type err: Exception
+ :param traceback: Traceback to be returned in debug mode.
+ :type traceback: string
+
:returns: A response object
"""
- data = json.dumps({
+ data = {
'error': six.text_type(err),
- })
+ }
+ if traceback and self.is_debug():
+ data['traceback'] = traceback
+ data = json.dumps(data)
status = getattr(err, 'status', 500)
return self.build_response(data, status=status)
@@ -257,10 +264,8 @@ class Resource(object):
data = view_method(*args, **kwargs)
serialized = self.serialize(method, endpoint, data)
except Exception as err:
- if self.is_debug():
- raise
-
- return self.build_error(err)
+ traceback = format_traceback(sys.exc_info())
+ return self.build_error(err, traceback)
status = self.status_map.get(self.http_methods[endpoint][method], OK)
return self.build_response(serialized, status=status)
diff --git a/restless/utils.py b/restless/utils.py
index 1e023d3..44ba0d6 100644
--- a/restless/utils.py
+++ b/restless/utils.py
@@ -1,5 +1,6 @@
import datetime
import decimal
+import traceback
try:
import json
@@ -82,3 +83,14 @@ class MoreTypesJSONEncoder(json.JSONEncoder):
return str(data)
else:
return super(MoreTypesJSONEncoder, self).default(data)
+
+
+def format_traceback(exc_info):
+ stack = traceback.format_stack()
+ stack = stack[:-2]
+ stack.extend(traceback.format_tb(exc_info[2]))
+ stack.extend(traceback.format_exception_only(exc_info[0], exc_info[1]))
+ stack_str = "Traceback (most recent call last):\n"
+ stack_str += "".join(stack)
+ stack_str = stack_str[:-1] # Remove the last \n
+ return stack_str
diff --git a/tests/test_dj.py b/tests/test_dj.py
index e59dcfd..843f341 100644
--- a/tests/test_dj.py
+++ b/tests/test_dj.py
@@ -185,8 +185,13 @@ class DjangoResourceTestCase(unittest.TestCase):
# Special-cased above for testing.
self.res.request = FakeHttpRequest('DELETE')
- with self.assertRaises(Unauthorized):
- self.res.handle('list')
+ # First with DEBUG on
+ resp = self.res.handle('list')
+ self.assertEqual(resp['Content-Type'], 'application/json')
+ self.assertEqual(resp.status_code, 401)
+ resp_json = json.loads(resp.content.decode('utf-8'))
+ self.assertEqual(resp_json['error'], 'Unauthorized.')
+ self.assertTrue('traceback' in resp_json)
# Now with DEBUG off.
settings.DEBUG = False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment