Created
February 19, 2014 21:23
-
-
Save jphalip/9101900 to your computer and use it in GitHub Desktop.
Suggested patch for restless
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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