Flask restful - Exception handling traceback? - python

I am using Flask Restful to build an API. I have a number of model classes with methods that may raise custom exceptions (for example: AuthFailed exception on my User model class). I am using the custom error handling, documented here, to handle this (so that when auth fails, an appropriate response is sent). So far so good. However, I notice that when the exception is raised although the correct response JSON and status is sent back, I still get a traceback which is not ideal. Usually, if I handle an error (outside of flask) with a try-except block, the except can catch the error and handle it (preventing the traceback). So what is the correct approach here? Am I misunderstanding how to use the errors feature?

Unfortunately for you, it is handled this way "by design" of the Flask-RESTful APIs errors functionality.
The exceptions which are thrown are logged and the corresponding response defined in the errors dict is returned.
However, you can change the level of log output by modifying the log level of Flask's logger like this:
app = Flask(__name__)
app.logger.setLevel(logging.CRITICAL)
I think you would actually have to set it to CRITICAL because these errors are still getting logged even on log level ERROR as far as I know.
Furthermore, both Flask and Flask-RESTful are open-source. That being said, after looking at the code I found the function of a Flask app that is responsible for adding the exception traceback to the log (Flask version 0.11.1).
Of course you could just create your own App class (extending the original class of Flask) which overrides this method (or a caller of it) and does something else instead. However, you should be careful when updating your Flask version if you make use of undocumented stuff like this.

Related

Which exception should I raise for a REST API error?

I am writing a simple API client in Python and I am wondering what exception should I raise if the remote server is not happy.
The API itself is very poorly documented (I don't know all the possible error messages so I can't just define custom classes for all of them), I am letting Requests handle HTTP-level errors which raises HTTPError in those cases, but what should I raise if the server just supplies an error key in the JSON response?
I am currently using Exception but that feels quite broad, I'd like to know whether there's a better alternative.
Regards.
Yes, just raising Exception is likely too broad.
Usually a module should define his own base exception class:
class MyAPIClientError(Exception):
pass
And then you can subclass that:
class RemoteServerNotHappyError(MyAPIClientError):
pass
RemoteServerNotHappyError should probably mention something about what the server's json returned that was not expected by your API client class. Add in the relevant error messages as you see fit.
This means users of your library can catch these specific exceptions (which they might know how to handle), without having to catch every exception (they surely don't know how to handle every possible failure mode).

Custom abort mapping/exceptions in Flask

The default message for Flask 400 exception (abort()) is:
{
"message": "The browser (or proxy) sent a request that this server could not understand."
}
For 404:
{
"message": "The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. You have requested this URI [/obj/] but did you mean /obj/ or /obj/<int:id>/ or /obj/<int:id>/kill/ ?"
}
I have trouble comprehending these messages when I'm getting them as replies in my API (especially the first one, I thought there's something wrong with encryption or headers) and I thing it's kinda tiresome to try to override text manually for every abort() exception. So I change the mapping:
from flask import abort
from werkzeug.exceptions import HTTPException
class BadRequest(HTTPException):
code = 400
description = 'Bad request.'
class NotFound(HTTPException):
code = 404
description = 'Resource not found.'
abort.mapping.update({
400: BadRequest,
404: NotFound
})
For the case of 400 it works beautifully. But when it comes to 404 it is still the same message. I tested it in the same place in my code - it works for abort(400), abort(403) and some of the others, but it gets mysteriously overridden by default message on abort(404). Debugging didn't help much. What may be the culprit here?
Update. Yes, I'm using abort imported from flask not flask_restful as the latter doesn't have the mapping and it's a function not an Aborter object. Besides, it does work for most exceptions, so it's probably not the real issue here.
Update 2. The abort.mapping seems to be perfectly fine on execution. The exceptions in question are overridden, including 404.
Update 3: I've put together a little sandbox, that I use for debugging. (removed the repo since the mystery is long solved).
It took me some time, but now I actually found the place, where it all derails on 404 error. It's actually an undocumented feature in flask-restful. Look at the code here. Whatever message you chose persists until that very place and then it becomes the default. What we need now is just put ERROR_404_HELP = False to our config and everything works as intended.
Why is this code even there in the first place? OK, perhaps, I can live with that, but it should be all over the documentation. Even when I googled the name of the constant, I only got a couple of GitHub issues (1, 2).
Anyways, the mystery is officially solved.
By the way... I can't point to documentation for how I discovered this, I just tried it (that's how I learn most development!) but, you can simply abort with the desired response code but instead return a custom string with it. I think this makes sense because you're using the framework the way it's intended, you're not writing tons of code, you're returning the correct response code and in the fashion the framework expects, and you're informing any human who reads it as to the application's context for the error.
from flask import abort
abort(404, "And here's why.")

Disable the "400 Bad Request" error for missing keys in form

The documentation for Flask explains that it will return a 400 Bad Request error if code attempts to access a form key that does not exist. This makes debugging difficult because the error doesn't have information about what key is missing or if something else caused the error.
Is there a way to disable the 400 error and return a "500 Internal Server Error" with a traceback in the logs instead?
If you are not sure if the form contains a key, you should use .get(key), which returns None if it is not found, rather than directly indexing it.
Typically, form validation should not raise an error at all, but should return messages about why validation failed. Consider using a form library such as WTForms to handle validation for you, so you get nice messages instead of errors.
If you still want to mess with the errors rather than doing validation, you can add a custom error handler to log the exception and return a generic 500 error.
from werkzeug.exceptions import abort, BadRequestKeyError
#app.errorhandler(BadRequestKeyError)
def handle_key_error(e):
app.logger.exception('Missing key {}'.format(e.args[0]))
# raise a new 500 exception
# abort doesn't work from inside an error handler
# so simulate how Flask would handle it
try:
abort(500)
except Exception as new_e:
app.handle_user_exception(new_e)
Your logging setup may be different. The app logger may require extra configuration when not in debug mode.
So I figured out a great solution. At least with Flask-restful, but I suspect that this is the case with the Flask-request in general. If you get a 400 Bad Request back, examining the JSON response from the server will give you more data. For example, in the Firefox development console, go to the network take, click on the request that generated the bad request, click the "Response" tab, and BAM, there's your useful debug error. Works at least in development mode on my own server.

What is the right way of handling python-social-auth exceptions when using Flask?

I'm adding PSU to a Flask app and all is going pretty well so far, but I can't figure out how to handle the exceptions raised by PSU. One such exception is social.exceptions.AuthCanceled, raised when a user decides to cancel the auth process. I would obviously want to catch that and display some message.
I found a similar question on how to do this in Django by creating a new Middleware. However, that approach seems to use middleware.py defined only in PSU's django_app (and not in the flask_app).
I have some experience with Flask but haven't added Middleware before, and I'm not sure this is the right direction.
UPDATE
Try defining an errorhandler (docs at http://flask.pocoo.org/docs/api/#flask.Flask.errorhandler), like this:
#app.errorhandler(500) # since exceptions will produce 500 errors
def error_handler(error):
if isinstance(error, SocialAuthBaseException):
return redirect('/error')
The solution below this line won't work
Try with a teardown_request (http://flask.pocoo.org/docs/reqcontext/#teardown-callbacks), like this
#app.teardown_request
def teardown_handler(exception=None):
if exception and isinstance(exception, SocialAuthBaseException):
return redirect('/error')

Accessing Request from __init__.py in Pyramid

When setting up a Pyramid app and adding settings to the Configurator, I'm having issues understanding how to access information from request, like request.session and such. I'm completely new at using Pyramid and I've searched all over the place for information on this but found nothing.
What I want to do is access information in the request object when sending out exception emails on production. I can't access the request object, since it's not global in the __init__.py file when creating the app. This is what I've got now:
import logging
import logging.handlers
from logging import Formatter
config.include('pyramid_exclog')
logger = logging.getLogger()
gm = logging.handlers.SMTPHandler(('localhost', 25), 'email#email.com', ['email#email.com'], 'Error')
gm.setLevel(logging.ERROR)
logger.addHandler(gm)
This works fine, but I want to include information about the logged in user when sending out the exception emails, stored in session. How can I access that information from __init__.py?
Attempting to make request a global variable, or somehow store a pointer to "current" request globally (if that's what you're going to try with subscribing to NewRequest event) is not a terribly good idea - a Pyramid application can have more than one thread of execution, so more than one request can be active within a single process at the same time. So the approach may appear to work during development, when the application runs in a single thread mode and just one user accesses it, but produce really funny results when deployed to a production server.
Pyramid has pyramid.threadlocal.get_current_request() function which returns thread-local request variable, however, the docs state that:
This function should be used extremely sparingly, usually only in unit
testing code. it’s almost always usually a mistake to use
get_current_request outside a testing context because its usage makes
it possible to write code that can be neither easily tested nor
scripted.
which suggests that the whole approach is not "pyramidic" (same as pythonic, but for Pyramid :)
Possible other solutions include:
look at exlog.extra_info parameter which should include environ and params attributes of the request into the log message
registering exception views would allow completely custom processing of exceptions
Using WSGI middleware, such as WebError#error_catcher or Paste#error_catcher to send emails when an exception occurs
if you want to log not only exceptions but possibly other non-fatal information, maybe just writing a wrapper function would be enough:
if int(request.POST['donation_amount']) >= 1000000:
send_email("Wake up, we're rich!", authenticated_userid(request))

Categories