Repository: marshmallow-code/webargs

Issue:
ValidationError causes HTTP/500 in Flask instead of HTTP/422 in Python 2
Here are the symptoms: http://stackoverflow.com/questions/37321835/flask-error-handler-not-able-to-handle-assertion-error/39624893#39624893

Here is the case where I personally hit the issue with Python 2.x and PyPy in Python 2.x mode only: https://travis-ci.org/frol/flask-restplus-server-example/builds/161685419

After troubleshooting, I discovered that this issue stems from the `FlaskParser.handle_error` which calls `abort` function with `exc=` argument, which then [is set as "data"](https://github.com/sloria/webargs/blob/dev/webargs/flaskparser.py#L38) on the raised `HTTPException`, which in its turn is tried to be json-dumped and fails with `TypeError`, and now the new exception is in `sys.exc_info()`, which causes Flask to [assert here](https://github.com/pallets/flask/blob/01081dbe6cdfa3fc43d8e1fff708d4ed95e1be7e/flask/app.py#L1515).

Searching git history for the introduced `exc=` in `FlaskParser`, I found it here: https://github.com/sloria/webargs/commit/6f8088c7c85c05d2891e81ee97d38f2ae801159d

Is there a reason for `exc=` to be passed? How can we fix this?

Python 3 seems to have changed something with `sys.exc_info()` behaviour since even after the `TypeError` caused by `json.dumps()`, `sys.exc_info()` still reports `HTTPException` while in Python 2 it returns `TypeError`.



Discussion:

Resolving patch:
diff --git a/webargs/flaskparser.py b/webargs/flaskparser.py
--- a/webargs/flaskparser.py
+++ b/webargs/flaskparser.py
@@ -25,7 +25,7 @@ def index(args):
 from webargs import core
 
 
-def abort(http_status_code, **kwargs):
+def abort(http_status_code, exc=None, **kwargs):
     """Raise a HTTPException for the given http_status_code. Attach any keyword
     arguments to the exception for later processing.
 
@@ -36,6 +36,7 @@ def abort(http_status_code, **kwargs):
     except HTTPException as err:
         if len(kwargs):
             err.data = kwargs
+        err.exc = exc
         raise err
 
 


Test patch:
diff --git a/tests/apps/flask_app.py b/tests/apps/flask_app.py
--- a/tests/apps/flask_app.py
+++ b/tests/apps/flask_app.py
@@ -150,5 +150,4 @@ def echo_use_kwargs_missing(username, password):
 # Return validation errors as JSON
 @app.errorhandler(422)
 def handle_validation_error(err):
-    exc = err.data['exc']
-    return J({'errors': exc.messages}), 422
+    return J({'errors': err.exc.messages}), 422
diff --git a/tests/test_flaskparser.py b/tests/test_flaskparser.py
--- a/tests/test_flaskparser.py
+++ b/tests/test_flaskparser.py
@@ -70,3 +70,19 @@ def test_abort_with_message():
     with pytest.raises(HTTPException) as excinfo:
         abort(400, message='custom error message')
     assert excinfo.value.data['message'] == 'custom error message'
+
+def test_abort_has_serializable_data():
+    with pytest.raises(HTTPException) as excinfo:
+        abort(400, message='custom error message')
+    serialized_error = json.dumps(excinfo.value.data)
+    error = json.loads(serialized_error)
+    assert isinstance(error, dict)
+    assert error['message'] == 'custom error message'
+
+    with pytest.raises(HTTPException) as excinfo:
+        abort(400, message='custom error message',
+            exc=ValidationError('custom error message'))
+    serialized_error = json.dumps(excinfo.value.data)
+    error = json.loads(serialized_error)
+    assert isinstance(error, dict)
+    assert error['message'] == 'custom error message'