title: Python error handling for flask expects json module throws internal server error
body: <p>I am using the flask_expects_json library to validate the JSON schema in API requests.</p>
  <p>This is working, however, when a request is not valid, the error is thrown in the logs but the details of the error message are not returned by the API. The response is just a generic message &quot;Internal Server Error&quot;.</p>
  <p>Error message:</p>
  <pre><code>{&quot;event&quot;: &quot;Exception on /ingest [PUT]&quot;, &quot;logger&quot;: &quot;app&quot;, &quot;level&quot;: &quot;error&quot;, &quot;exception&quot;: &quot;Traceback (most recent call last):\n  File \&quot;/usr/local/lib/python3.8/site-packages/flask_expects_json/__init__.py\&quot;, line 30, in decorated_function\n    validate(data, schema)\n  File \&quot;/usr/local/lib/python3.8/site-packages/jsonschema/validators.py\&quot;, line 934, in validate\n    raise error\njsonschema.exceptions.ValidationError: 'location' is a required property\n\nFailed validating 'required' in schema}
  </code></pre>
  <p>Response:</p>
  <pre><code>{
      &quot;message&quot;: &quot;Internal Server Error&quot;
  }
  </code></pre>
  <p>Question: How do I handle the errors from flask_expects_json and return the details of the error instead of the server error? In other words, I need the details of the error in the response, such as:</p>
  <pre><code>{
      &quot;error&quot;: &quot;'location' is a required property&quot;
  }
  </code></pre>
  <p>Here is the code I have so far.</p>
  <pre><code>from flask import Flask, request, jsonify, current_app
  from flask_restx import Resource, Api
  from flask_expects_json import expects_json
  
  def create_app():
    app = Flask(__name__)
    api = Api(app, version='1.0', title='Test API', description='Test API Description')
  
    @api.route('/ingest', endpoint=&quot;ingest&quot;, methods=['GET', 'POST', 'PUT', 'DELETE'])
    class Ingest(Resource):
      @expects_json(test_schema)
      def get(self, *args, **kwargs):
        # logic removed for brevity
        result = get_result()
        return result
  
    return app
  
  if __name__ == '__main__':
    app.run()
  </code></pre>
  <p>I also tried writing an error handler to catch these errors as per the <a href="https://flask.palletsprojects.com/en/1.1.x/patterns/apierrors/" rel="nofollow noreferrer">flask documentation on error handling</a> and other <a href="https://stackoverflow.com/questions/54377654/python-flask-handling-application-errors">similar Stack Overflow questions</a> but it does not appear to be doing anything.</p>
  <pre><code>class BadRequest(Exception):
    status_code = 400
  
    def __init__(self, message, status_code=None, payload=None):
      Exception.__init__(self)
      self.message = message
      if status_code is not None:
        self.status_code = status_code
      self.payload = payload
  
    def to_dict(self):
      rv = dict(self.payload or ())
      rv['message'] = self.message
      return rv
  
    @app.errorhandler(BadRequest)
    def handle_bad_request(error):
      response = jsonify(error.to_dict())
      response.status_code = error.status_code
      return response
  </code></pre>
  <p>UPDATE: This error is probably caused by the gunicorn structlog configuration that is attempting to convert a ValidationError to JSON and failing therefore throwing the 500 error.</p>
  <p>Here is the gunicorn.conf.py</p>
  <pre><code>import structlog
  import os
  from datetime import datetime
  
  if os.environ.get('ENV') == 'development':
    reload = True
  
  hostname = os.uname().nodename
  timestamp = datetime.today().strftime('%Y-%m-%d')
  
  pre_chain = [
      structlog.stdlib.add_logger_name,
      structlog.stdlib.add_log_level,
      structlog.stdlib.PositionalArgumentsFormatter(),
      structlog.processors.StackInfoRenderer(),
      structlog.processors.format_exc_info,
      structlog.processors.UnicodeDecoder(),
      structlog.processors.TimeStamper(fmt='iso', utc=True),
  ]
  
  logconfig_dict = {
      &quot;version&quot;: 1,
      &quot;disable_existing_loggers&quot;: True,
      &quot;formatters&quot;: {
          &quot;json_formatter&quot;: {
              &quot;()&quot;: structlog.stdlib.ProcessorFormatter,
              &quot;processor&quot;: structlog.processors.JSONRenderer(),
              &quot;foreign_pre_chain&quot;: pre_chain,
          }
      },
      &quot;handlers&quot;: {
          &quot;error_console&quot;: {
              &quot;class&quot;: &quot;logging.FileHandler&quot;,
              &quot;formatter&quot;: &quot;json_formatter&quot;,
              &quot;filename&quot;: &quot;/home/logs/error_console_{}_{}.log&quot;.format(hostname, timestamp),
              &quot;mode&quot;: &quot;a&quot;
          },
          &quot;console&quot;: {
              &quot;class&quot;: &quot;logging.FileHandler&quot;,
              &quot;formatter&quot;: &quot;json_formatter&quot;,
              &quot;filename&quot;: &quot;/home/logs/console_{}_{}.log&quot;.format(hostname, timestamp),
              &quot;mode&quot;: &quot;a&quot;
          }
      },
      &quot;loggers&quot;: {
          'gunicorn.error': {
              'handlers': ['console'],
              'level': os.environ.get('APP_LOG_LEVEL', 'INFO'),
              'propagate': False,
          },
          'gunicorn.access': {
              'handlers': ['console'],
              'level': os.environ.get('APP_LOG_LEVEL', 'INFO'),
              'propagate': False,
          }
      }
  }
  </code></pre>
  <p>Here is the 500 error that is occurring when it attempts to throw the 400 Bad Request error but then runs into a TypeError. <code>TypeError: Object of type ValidationError is not JSON serializable</code>.</p>
  <pre><code>Failed validating 'required' in schema
     
  During handling of the above exception, another exception occurred:
  
  Traceback (most recent call last):
    
    File &quot;/usr/local/lib/python3.8/json/__init__.py&quot;, line 231, in dumps
      return _default_encoder.encode(obj)
    File &quot;/usr/local/lib/python3.8/json/encoder.py&quot;, line 199, in encode
      chunks = self.iterencode(o, _one_shot=True)
    File &quot;/usr/local/lib/python3.8/json/encoder.py&quot;, line 257, in iterencode
      return _iterencode(o, 0)
    File &quot;/usr/local/lib/python3.8/json/encoder.py&quot;, line 179, in default
      raise TypeError(f'Object of type {o.__class__.__name__} '
  TypeError: Object of type ValidationError is not JSON serializable
  </code></pre>
  <p>UPDATE 2 [WORKAROUND]: As mentioned I think this issue is due to an error handling issue when expects_json throws a ValidationError. It might be related to the gunicorn structlog config or it might be something that can be fixed in the expects_json library. As a workaround I removed expects_json and created a custom decorator to validate the json schema using the jsonschema library similar to the accepted answer in this related question. <a href="https://stackoverflow.com/questions/24238743/flask-decorator-to-verify-json-and-json-schema">Flask: Decorator to verify JSON and JSON Schema</a></p>
  <p>Here is the full code of my solution. Again this does not actually solve the issue directly but is an acceptable workaround and does provide a bit more control.</p>
  <pre><code>from flask import request, jsonify, make_response, current_app
  import os, json, jsonschema
  
  def validate_json_schema(schema_name):
    def decorator(f):
      def wrapper(*args, **kwargs):
  
        basedir = current_app.config['BASE_DIR']
        jsonmodelsdir = basedir + '/jsonmodels'
  
        if not request.json:
          error_msg = &quot;Missing JSON data.&quot;
          return make_response(jsonify({'error': 'invalidRequest', 'message': error_msg}), 400)
  
        try:
          with open('{}/{}.json'.format(jsonmodelsdir, schema_name)) as json_file:
            json_model = json.load(json_file)
        except Exception as e:
          error_msg = &quot;Server error: Unable to get json model.&quot;
          current_app.logger.error(error_msg)
          current_app.logger.error(e)
          return make_response(jsonify({'error': 'unavailable', 'message': error_msg}), 500)
  
        try:
          jsonschema.validate(request.json, json_model)
        except json.decoder.JSONDecodeError as e:
          error_msg = &quot;Invalid JSON format: {}&quot;.format(e)
          return make_response(jsonify({'error': 'invalidRequest', 'message': error_msg}), 400)
        except jsonschema.exceptions.ValidationError as e:
          error_msg = &quot;Invalid JSON schema: {}&quot;.format(e)
          return make_response(jsonify({'error': 'invalidRequest', 'message': error_msg}), 400)
        except Exception as e:
          error_msg = &quot;Server error: Unable to validate json model.&quot;
          current_app.logger.error(e)
          return make_response(jsonify({'error': 'unavailable', 'message': error_msg}), 500)
  
        return f(*args, **kwargs)
      return wrapper
    return decorator
  </code></pre>