I'm currently writing a few end points for an API in fastAPI.
I'm defining classes that extend fastapi's HTTPException.
The problem is that HTTPException returns a response body with an attribute called detail which will either be a string or a json structure depending on what object you pass to it as seem below.
{
"detail": {
"msg": "error message here"
}
}
{ "detail": "error message here" }
I would like to override this behavior and let it respond with my own structure.
I'm aware that i can install custom exceptions using the exception handler decorator and have that return a JSONResponse object but this is not what i'm looking for.
One option is to set the status code corresponding to your exception, and then return a custom response body.
Here is a simple toy example using this approach to workaround the issue at hand:
from fastapi import FastAPI, Response, status
myapp = FastAPI()
#myapp.get("/")
async def app_func(response: Response, myparam: str):
#end point code here
valid_params = ['a', 'b', 'c']
if myparam not in valid_params:
#Customize logic, status code, and returned dict as needed
response.status_code = status.HTTP_400_BAD_REQUEST
return {'message': 'Oh no! I failed (without detail key)'}
response.status_code = status.HTTP_200_OK
return {'message': 'Yay! I succeeded!'}
A full list of available status codes can be found here.
Related
I have a Flask application that returns both HTML pages and JSON responses to API requests. I want to change what an error handler returns based on the content type of the request. If the client requests application/json, I want to return a jsonify response, otherwise I want to return a render_template response. How can I detect what was requested and change the response appropriately?
The current error handlers I have only return an HTML response.
def register_errorhandlers(app):
"""Register error handlers."""
def render_error(error):
"""Render error template."""
# If a HTTPException, pull the `code` attribute; default to 500
error_code = getattr(error, 'code', 500)
return render_template('{0}.html'.format(error_code)), error_code
for errcode in [401, 404, 500]:
app.errorhandler(errcode)(render_error)
Use request.content_type to get the content type the client sent with the request. Use request.accept_mimetypes the get the mimetypes the client indicated it can accept in a response. Use these to determine what to return.
from flask import request, jsonify, render_template
if request.accept_mimetypes.accept_json:
return jsonify(...)
else:
return render_template(...)
I used the after_request decorator to do this and checked the content type:
#app.after_request
def after_request_helper(resp):
if resp.content_type == "text/html":
# If a HTTPException, pull the `code` attribute; default to 500
error_code = getattr(error, 'code', 500)
return render_template('{0}.html'.format(error_code)), error_code
else:
return app.errorhandler(errcode)(render_error)
A more detailed answer:
def wants_json_response():
return request.accept_mimetypes['application/json'] >= \
request.accept_mimetypes['text/html']
The wants_json_response() helper function compares the preference for JSON or HTML selected by the client in their list of preferred formats. If JSON rates higher than HTML, then it is necessary to return a JSON response.
Otherwise, return the original HTML responses based on templates.
For the JSON responses would slightly supplement the function with one condition:
if wants_json_response(): which is what you need. So the answer is in that.
If the condition is true we could write a function that would generate a response:
def api_error_response(status_code, message=None):
payload = {'error': HTTP_STATUS_CODES.get(status_code, 'Unknown error')}
if message:
payload['message'] = message
response = jsonify(payload)
response.status_code = status_code
return response
This function uses the handy HTTP_STATUS_CODES dictionary from Werkzeug (a core dependency of Flask) that provides a short descriptive name for each HTTP status code.
For easier and faster understanding, 'error' is used to represent errors, so you only need to worry about the numeric status code and the optional long description.
The jsonify() function returns a Flask Response object with a default status code of 200, so after the response is created, it is necessary to set the status code to the correct one for the error.
So if we put it all together now it would look like this:
# app/__init__.py
import requests
def register_errorhandlers(app):
from .errors import render_error
for e in [
requests.codes.INTERNAL_SERVER_ERROR,
requests.codes.NOT_FOUND,
requests.codes.UNAUTHORIZED,
]:
app.errorhandler(e)(render_error)
and
# app/errors.py
import requests
from flask import render_template, request, jsonify
from werkzeug.http import HTTP_STATUS_CODES
from .extensions import db
def api_error_response(status_code, message=None):
payload = {'error': HTTP_STATUS_CODES.get(status_code, 'Unknown error')}
if message:
payload['message'] = message
response = jsonify(payload)
response.status_code = status_code
return response
def wants_json_response():
return request.accept_mimetypes['application/json'] >= \
request.accept_mimetypes['text/html']
def render_error(e):
if requests.codes.INTERNAL_SERVER_ERROR == e.code:
db.session.rollback()
if wants_json_response():
return api_error_response(e.code)
else:
return render_template(f'{e.code}.html'), e.code
Additionally
Then they could use the response generation for other cases as well.
The most common error that the API is going to return is going to be
the code 400, which is the error for “bad request”. This is the error
that is used when the client sends a request that has invalid data in it.
In order to generate messages to the function below even easier in these cases, we forward only the required description - message.
def bad_request(message):
return api_error_response(400, message)
I hope this will help in approaching with errors :)
I'm writing Falcon middleware for my application. When i get any errors i want to raise error, break process and return my custom response, that looks like:
{
"status": 503,
"message": "No Token found. Token is required."
}
But standard Falcon error implementation does not allow me to set custom fields to my response.
How to solve this problem most properly?
After a lot of time spent, I solved this problem in such interesting way. I put my code in a try/catch block, and when an error is caught I decided not to raise Falcon error, and just tried to write return keyword after setting response status and body, because the method is void, so it does not return anything. Now it looks like:
resp.status = falcon.HTTP_403
resp.body = body
return
I was still looking for an example and here is for anyone who still need it:
from falcon.http_error import HTTPError
class MyHTTPError(HTTPError):
"""Represents a generic HTTP error.
"""
def __init__(self, status, error):
super(MyHTTPError, self).__init__(status)
self.status = status
self.error = error
def to_dict(self, obj_type=dict):
"""Returns a basic dictionary representing the error.
"""
super(MyHTTPError, self).to_dict(obj_type)
obj = self.error
return obj
using:
error = {"error": [{"message": "Auth token required", "code": "INVALID_HEADER"}]}
raise MyHTTPError(falcon.HTTP_400, error)
Create custom exception class explained in falcon docs, search for add_error_handler
class RaiseUnauthorizedException(Exception):
def handle(ex, req, resp, params):
resp.status = falcon.HTTP_401
response = json.loads(json.dumps(ast.literal_eval(str(ex))))
resp.body = json.dumps(response)
Add custom exception class to falcon API object
api = falcon.API()
api.add_error_handler(RaiseUnauthorizedException)
raise falcon.HTTPError(falcon.HTTP_503, 'No Token found. Token is required.')
I am developping a JSON API with Python Flask.
What I want is to always return JSON, with a error message indicating any error that occured.
That API also only accept JSON data in the POST body, but Flask by default return a HTML error 400 if it can't read the data as JSON.
Preferably, I d also like to not force the user to send the Content-Type header, and if raw or text content-type, try to parse the body as JSON nonetheless.
In short, I need a way to validate that the POST body's is JSON, and handle the error myself.
I've read about adding decorator to request to do that, but no comprehensive example.
You have three options:
Register a custom error handler for 400 errors on the API views. Have this error return JSON instead of HTML.
Set the Request.on_json_loading_failed method to something that raises a BadRequest exception subclass with a JSON payload. See Custom Errors in the Werkzeug exceptions documentation to see how you can create one.
Put a try: except around the request.get_json() call, catch the BadRequest exception and raise a new exception with a JSON payload.
Personally, I'd probably go with the second option:
from werkzeug.exceptions import BadRequest
from flask import json, Request, _request_ctx_stack
class JSONBadRequest(BadRequest):
def get_body(self, environ=None):
"""Get the JSON body."""
return json.dumps({
'code': self.code,
'name': self.name,
'description': self.description,
})
def get_headers(self, environ=None):
"""Get a list of headers."""
return [('Content-Type', 'application/json')]
def on_json_loading_failed(self):
ctx = _request_ctx_stack.top
if ctx is not None and ctx.app.config.get('DEBUG', False):
raise JSONBadRequest('Failed to decode JSON object: {0}'.format(e))
raise JSONBadRequest()
Request.on_json_loading_failed = on_json_loading_failed
Now, every time request.get_json() fails, it'll call your custom on_json_loading_failed method and raise an exception with a JSON payload rather than a HTML payload.
Combining the options force=True and silent=True make the result of request.get_json be None if the data is not parsable, then a simple if allow you to check the parsing.
from flask import Flask
from flask import request
#app.route('/foo', methods=['POST'])
def function(function = None):
print "Data: ", request.get_json(force = True, silent = True);
if request.get_json() is not None:
return "Is JSON";
else:
return "Nope";
if __name__ == "__main__":
app.run()
Credits to lapinkoira and Martijn Pieters.
You can try to decode JSON object using python json library.
The main idea is to take plain request body and try to convert to JSON.E.g:
import json
...
# somewhere in view
def view():
try:
json.loads(request.get_data())
except ValueError:
# not a JSON! return error
return {'error': '...'}
# do plain stuff
I am trying to catch the Exception which is raised when the url provided is a messy and wrong url and then return the error response as JSON. This is what i did to implement this logic.
The exception is raised inside the Analysis class when the key_id is not a valid key for S3.
def url_error(status_code, message, reason):
response = jsonify({
'status': status_code,
'message': message,
'reason': reason
})
response.status_code = status_code
return response
class RowColumnCount(Resource):
def get(self, key_id):
try:
rc = Analysis(key_id=key_id)
except S3ResponseError as e:
return url_error(e.status, e.message, e.reason)
json_response = json.loads(rc.count_rows_columns())
return json_response
The above code works fine but its kinda getting repetitive for 50 different Resource classes. Each Resource class should handle this specific error. How to make it a decorator, such that code repetitiveness is reduced.
I am using Flask, Flask-Restful, Python 3.4.3
There are a couple of ways you can achieve what you're trying to do but I think the cleanest way is to extend the Resource class as described in the Flask-Restful docs here and create a new decorator whose job is to catch the S3ResponseError and return the appropriate response. You can then subclass all your resources from your new base Resource class.
Also I would suggest you specify an API level json_output method as described here and here so that way all you have to do is return a dict from any of your resources and they'll be converted to JSON appropriately.
Using a python flask server, I want to be able to throw an http error response with the abort command and use a custom response string and a custom message in the body
#app.errorhandler(400)
def custom400(error):
response = jsonify({'message': error.message})
response.status_code = 404
response.status = 'error.Bad Request'
return response
abort(400,'{"message":"custom error message to appear in body"}')
But the error.message variable comes up as an empty string. I can't seem to find documentation on how to get access to the second variable of the abort function with a custom error handler
If you look at flask/__init__.py you will see that abort is actually imported from werkzeug.exceptions. Looking at the Aborter class, we can see that when called with a numeric code, the particular HTTPException subclass is looked up and called with all of the arguments provided to the Aborter instance. Looking at HTTPException, paying particular attention to lines 85-89 we can see that the second argument passed to HTTPException.__init__ is stored in the description property, as #dirn pointed out.
You can either access the message from the description property:
#app.errorhandler(400)
def custom400(error):
response = jsonify({'message': error.description['message']})
# etc.
abort(400, {'message': 'custom error message to appear in body'})
or just pass the description in by itself:
#app.errorhandler(400)
def custom400(error):
response = jsonify({'message': error.description})
# etc.
abort(400, 'custom error message to appear in body')
People rely on abort() too much. The truth is that there are much better ways to handle errors.
For example, you can write this helper function:
def bad_request(message):
response = jsonify({'message': message})
response.status_code = 400
return response
Then from your view function you can return an error with:
#app.route('/')
def index():
if error_condition:
return bad_request('message that appears in body')
If the error occurs deeper in your call stack in a place where returning a response isn't possible then you can use a custom exception. For example:
class BadRequestError(ValueError):
pass
#app.errorhandler(BadRequestError)
def bad_request_handler(error):
return bad_request(str(error))
Then in the function that needs to issue the error you just raise the exception:
def some_function():
if error_condition:
raise BadRequestError('message that appears in the body')
I hope this helps.
I simply do it like this:
abort(400, description="Required parameter is missing")
flask.abort also accepts flask.Response
abort(make_response(jsonify(message="Error message"), 400))