python-3.xhttphttp-status-code-404flask-sqlalchemyhttp-status-code-422

Failing to send 404 HTTP status on Flask when client tries to get a nonexistent element


In a Python/Flask application, I have defined this endpoint that I expect to return 404 if a client tries to get an id that doesn't exist on my database.

For example:

@app.route('/plants/<int:plant_id>', methods=['GET'])
def get_plant(plant_id):
  try:
    plant = Plant.query.filter(Plant.id == plant_id).one_or_none()

    if plant is None:
      abort(404)

    return jsonify({
      'success': True,
      'plant': plant.format()
    })

  except:
    abort(422)

The problem is that when I try to execute it, it always seems to raise an exception and returns 422.

GET nonexistent plant on Postman returning 422

If I remove the try/except syntax, it works as expected and returns the 404. But I lose the capacity of handling exceptions... so it's not a solution for me.

Why am I doing wrong? How could I correctly trigger 404 without setting 404 as the except return?

Thanks!!


Solution

  • Ok, finally I was able to understand it and solve it. I post my findings here so maybe it could help someone in the future. :)

    The answer is very basic, actually: every time I abort, I trigger an exception. So, when I aborted, no matter the status code I used, I fell into my except statement, which was returning 422 by default.

    What I did to solve it was to implement a custom RequestError, and every time I have a controlled error, I trigger my custom error, which output I can control separately.

    This is the implementation of my custom error:

    class RequestError(Exception):
        def __init__(self, status):
            self.status = status
        def __str__(self):
            return repr(self.status)
    

    And I've changed my route implementation for something like this: (note that I'm now handling first the custom error exception, and only then triggering a generic 422 error)

    @app.route('/plants/<int:plant_id>', methods=['GET'])
    def get_plant(plant_id):
      try:
        plant = Plant.query.filter(Plant.id == plant_id).one_or_none()
    
        if plant is None:
          raise RequestError(404)
    
        return jsonify({
          'success': True,
          'plant': plant.format()
        })
    
      except RequestError as error:
        abort(error.status)
    
      except:
        abort(422)
    

    And that does it! \o/