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
Related
I'm trying to send two files in a json object with flask restfull and send_file.
When I try to access the json object on client side i get this error:
TypeError: Object of type 'Response' is not JSON serializable
This is what i do i my flask app:
class Download(Resource):
def get(self):
try:
return {"file1" : send_file('uploads/kode.png'), "file2" : send_file('uploads/kode.png')}, 200
except:
return {"message" : "File not found!"}, 404
How can I return a json of files?
If I do the same thing with only one file without wrapping the send_file() in {}, I can access that file on the front end with java script.
You mean like:
return send_file('path/to/file.png')
That works, because Flask's send_file function actually returns a Response object.
This is also valid code (as of flask version 1.1.0):
return {"message" : "File not found!"}, 404
Here you're returning a dictionary with the key 'message' and value 'File not found!'. Flask will turn this into a Response object, with a status code of 404.
That dictionary is jsonified automatically (as of flask version 1.1.0).
When you try to return this:
return {"file1" : send_file('uploads/kode.png')}, 200
The Response object returned by send_file is then jsonified, hence the exception:
TypeError: Object of type 'Response' is not JSON serializable
The obvious way to make this work is that the frontend should make a separate request to the server for each image, passing some kind of ID which the Flask route function should then obtain and use to work out the filepath, then ultimately: return sendfile(filepath).
If you really want to send several images in one JSON response, you could look at base64 encoding the image and making a data_uri string which can be JSON serialized. However unless you really need that data to be passed as JSON, the former option is probably the go-to.
I think your except is too broad. but to get a json object back, import json and its object.json()
import json
try:
return file.json()
except Exception as e:
print(e)
or your can import the json library from flask
from flask import jsonify
def myMethod():
....
response = jsonify(data)
response.status_code = 200 # or 400 or whatever
return response
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 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.
My webservice should receive calls in these two formats: application/x-www-form-urlencoded and content-type application/json.
The code below works correctly for the forms. However, it doesn't work for the json ones. Apparently I need to use request.args.get for it.
Is there a way to modify the code so that the same method can receive calls in these two formats?
#app.route("/api/<projectTitle>/<path:urlSuffix>", methods=['POST'])
def projectTitlePage(projectTitle, urlSuffix):
apiKey = request.form.get('apikey')
userId = databaseFunctions.getApiKeyUserId(apiKey)
userInfo = databaseFunctions.getUserInfo(userId)
projectId = databaseFunctions.getTitleProjectId(projectTitle)
projectInfo = databaseFunctions.getProjectInfo(projectId)
databaseFunctions.addUserHit(userId, projectId)
databaseFunctions.addProjectHit(userId)
print request.form.to_dict(flat=False)
try:
r = requests.post(projectInfo['secretUrl'], data=request.form.to_dict(flat=False))
except Exception, e:
return '/error=Error'
return r.text
Try to get the JSON using Request.get_json(); an exception is raised if that fails, after which you can fall back to using request.form:
from flask import request
from werkzeug.exceptions import BadRequest
try:
data = request.get_json()
apiKey = data['apikey']
except (TypeError, BadRequest, KeyError):
apiKey = request.form['apikey']
If the mimetype is not application/json, request.get_json() returns None; trying to use data['apikey'] then results in a TypeError. The mimetype being correct but the JSON data being invalid gives you a BadRequest, and all other invalid return values either result in a KeyError (no such key) or a TypeError (object doesn't support indexing by name).
The other option would be to test the request.mimetype attribute:
if request.mimetype == 'application/json':
data = request.get_json()
apiKey = data['apiKey']
else:
apiKey = request.form['apikey']
Either way, if there is no valid JSON data or form data was posted but there is no apikey entry or an unrelated mimetype was posted, a BadRequest exception will be raised and a 400 response is returned to the client.
I'm not incredibly familiar with Flask in particular, but based on their documentation you should be able to do something like
content = request.headers['CONTENT-TYPE']
if content[:16] == 'application/json':
# Process json
else:
# Process as form-encoded
My approach to fetch the json and form-data regardless of the header was like
data = request.get_json() or request.form
key = data.get('key')
Given a simple Flask application, I'm just curious about whether there is a proper way to modify a Response in the hooks such as process_response?
e.g. Given:
from flask import Flask, Response
class MyFlask(Flask):
def process_response(self, response):
# edit response data, eg. add "... MORE!", but
# keep eg mimetype, status_code
response.data += "... This is added" # but should I modify `data`?
return response
# or should I:
# return Response(response.data + "... this is also added",
# mimetype=response.mimetype, etc)
app = MyFlask(__name__)
#app.route('/')
def root():
return "abddef"
if __name__ == '__main__':
app.run()
Is it proper to just create a new response each time, or is it canonical to just edit in-place the response parameter and return that modified response?
This may be purely stylistic, but I'm curious – and I haven't noticed anything in my reading that would indicate the preferred way to do this (even though it's probably quite common).
Thanks for reading.
From the Flask.process_response docs:
Can be overridden in order to modify the response object before it's sent to the WSGI server.
The response object is created on flask dispacher mechanism (Flask.full_dispatch_request). So if you want to create response objects under your own way, override Flask.make_reponse. Use Flask.process_response only when the desired modifications can be made using the created response object parameter.
Actually, you can use Flask.process_response to intercept and modify the response this way:
from flask import Flask
import json
import ast
appVersion = 'v1.0.0'
class LocalFlask(Flask):
def process_response(self, response):
#Every response will be processed here first
response.headers['App-Version'] = appVersion
success = True if response.status_code in [ 200, 201, 204 ] else False
message = 'Ok' if success else 'Error'
dict_str = response.data.decode("UTF-8")
dataDict = ast.literal_eval(dict_str)
standard_response_data = {
'success': success,
'message': message,
'result': dataDict
}
response.data = json.dumps(standard_response_data)
super(LocalFlask, self).process_response(response)
return response