flask-smorest icon indicating copy to clipboard operation
flask-smorest copied to clipboard

Using abort inside custom error handler?

Open TheBigRoomXXL opened this issue 3 years ago • 1 comments

I wanted to avoid wrapping all my sqlachemy queries with annoying try-catch so I created the follow error handler:

def handle_sqlachemy_dbapi_errors(e):
  db.session.rollback()
  if e.orig.pgcode == UNIQUE_VIOLATION:
    abort(409, errors=e.__class__.__name__, message=e.orig.diag.message_detail)
  if e.orig.pgcode == FOREIGN_KEY_VIOLATION or e.orig.pgcode == INVALID_TEXT_REPRESENTATION:
    abort(422, errors=e.__class__.__name__, message=e.orig.diag.message_detail)
  else : 
    abort(400, errors=e.__class__.__name__, message=e.orig.diag.message_detail)

What I didn't expect is that Smorest HTTP Handler doesn't catch the abort and instead a 500 Internal Server Error is raised.

I tried to register it at the App and Blueprint level, but it didn't work in both cases.

Is that a normal behaviour for abort? I am not very familiar with error handling in flask but, I guess you can't use an error handler inside another even if they catch different errors? Any idea for a workaround?

Thank you.

TheBigRoomXXL avatar Jul 25 '22 13:07 TheBigRoomXXL

Good question. I've been achieving the same purpose by adding a decorator around view functions but an error handler approach would be even better.

Can't look into this right now but definitely worth investigating. Thanks for reporting.

lafrech avatar Jul 27 '22 19:07 lafrech

Flask does not forward exceptions from error handlers to other error handlers again (all exceptions default to a 500 http error for the request). This is probably to avoid infinite loops.

If you want to pass an exception to any registered error handler (e.g. the HTTPError from abort) you need to call handle_user_exception(error) or handle_http_exception(http_error) on the current app manually if you are inside an error handler.

buehlefs avatar Aug 29 '22 11:08 buehlefs

Thank you for the answer.

I ended up just formating the response directly inside my custom handler to mimic the smorest error handler. Not the most elegant answer but it's a simpler than forwarding errors.

def handle_dbapi_errors(e):
  """Handle database error comming from SQLAchemy. 
  
  This handler is used to not have try catch everywhere in our controller and ensure a standart output."""

  db.session.rollback()
  message =  e.orig.diag.message_detail or e.orig.diag.message_primary
  if e.orig.pgcode == UNIQUE_VIOLATION:
    result = {
      "code" : 409, 
      "status" : "Conflict", 
      "errors" : {"Unique Constraint Violation" : message},  
    }
  elif e.orig.pgcode == FOREIGN_KEY_VIOLATION :
    result = {
      "code" : 422, 
      "status" : "Unprocessable Entity", 
      "errors" : {"Foreign Key Constraint Violation" : message}, 
    }
  elif e.orig.pgcode in value_errors_list:
    result = {
      "code" : 422, 
      "status" : "Unprocessable Entity", 
      "errors" : {"Value Error" : message}, 
    }
  elif e.orig.pgcode == CHECK_VIOLATION:
    result = {
      "code" : 422, 
      "status" : "Unprocessable Entity", 
      "errors" : {"Value Error" : e.orig.diag.message_primary}, 
    }
  else : 
    print(f"Unexpected DBAPI Error: {e.__class__.__name__}, {str(e)} ")
    result = {"code" : 500, "status" : "Internal Server Error"}
  return result, result["code"]

TheBigRoomXXL avatar Aug 29 '22 13:08 TheBigRoomXXL