Which exception should I raise for a REST API error? - python

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).

Related

Graphene is sending Python error messages in its response - how do I tell it to be more discrete?

I have implemented a GraphQL server in Python using Django and Graphene. I have mutations that use modelforms, which broadly look like this:
def mutate(self, info, **kwargs):
form = MyModelForm(kwargs)
if form.is_valid():
form.save()
return MyMutation(instance=form.instance)
raise GraphQLError(json.dumps(form.errors))
That is, if form validation fails, send the form errors as a JSON string, so that the frontend can neatly unpack the errors. This all works fine.
However, if there is some other error - say there's a problem with the database which throws an exception when the form tries to save, or there's a typo, then the default behaviour is for graphene to take the error message and transmit it in the GraphQL errors list to the client. Which means that anyone who inspects the response can see raw error messages from the server.This seems unwise - what if that error message contains information I don't want leaking? Obviously the solution is to create an application that doesn't throw exceptions, but I can't anticipate everything. Is there no way to tell graphene to be a bit more discrete, and provide a generic error message when an error isn't caused by me explicitly raising a GraphQLError, rather than just sending everything to the client?
An example of what is currently happening:
{"errors":[{"message":"name 'LogiForm' is not defined","locations":[{"line":2,"column":3}],"path":["login"]}],"data":{"login":null}}
This particular example isn't a security problem and would be easily caught before production, but it's just an example - and even when not a security threat, it seems a little unprofessional for my server to be making its internal error messages viewable by the client.
I am currently solving this with a decorator:
def hide_error(mutate):
def new_func(*args, **kwargs):
try:
return mutate(*args, **kwargs)
except Exception as e:
raise e if isinstance(e, GraphQLError) else Exception("Error.")
return new_func
But is there a better way? Is there a way you're 'supposed' to manage this problem?

Properly catch boto3 Errors

I am developing a django app which communicates with several Amazon Web Services.
So far I am having trouble dealing with and catching exceptions thrown by the boto3 client. What I am doing seems unnecessarily tedious:
Example:
client = boto3.client('sns')
client.create_platform_endpoint(PlatformApplicationArn=SNS_APP_ARN, Token=token)
this might throw an botocore.errorfactory.InvalidParameterException if e.g. the token is bad.
client.get_endpoint_attributes(EndpointArn=endpoint_arn)
might throw an botocore.errorfactory.NotFoundException.
First, I can't find these Errors anywhere in code, so they are probably generated somewhere. Bottom line: I can't import it and catch it as usual.
Second, I found one way to catch the error here using:
try:
# boto3 stuff
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == 'NotFound':
# handle exception
else:
raise e
But I have to remove the Exception part of the error name. Seems very random and I have no clue whether I would remove the Error in botocore.exceptions.ParamValidationError if I wanted to catch that one. So it's hard to generalize.
Another way to catch the error is using the boto3 client object I got:
try:
# boto3 stuff
except client.exceptions.NotFoundException as e:
# handle exception
This seems the cleanest way so far. But I don't always have the boto3 client object at hand where I want to catch the error. Also I am still only trying things out, so it's mostly guess work.
Does anybody know how boto3 errors are supposed to be handled?
Or can point me towards some coherent documentation which mentions the errors above? Thanks
You've summarized the situation well. The old boto had a simple hardcoded approach to supporting AWS APIs. boto3, in what appears to be an attempt to reduce the overhead of keeping Python client synced with evolving features on the various apis, has been more squishy around exceptions, so the ClientError approach you outlined above used to be the canonical way.
In 2017 they introduced the second mechanism you highlight: 'modeled' exceptions available on the client.
I am not familiar with SNS but in my experience with other AWS products, the ClientError naming matches up with the HTTP apis, which tend to be well documented. So I would start here: https://docs.aws.amazon.com/sns/latest/api/Welcome.html
It looks like the new-style modeled exceptions are generated from service definition files that live in botocore module. I can't find any documentation about it, but go browse around the AWS service models in https://github.com/boto/botocore/tree/master/botocore/data.
Also, it's good to know that if you are not (in contrast to OP's code) dealing directly with the low-level client, but instead are using a high-level AWS ServiceResource object, a low-level client is still easily available at my_service_resource.meta.client so you can handle exceptions like this:
try:
my_service_resource.do_stuff()
except my_service_resource.meta.client.exceptions.NotFoundException as e:
# handle exception
Use Boto3 exceptions: https://www.programcreek.com/python/example/97944/boto3.exceptions
client = boto3.client('logs')
try:
client.create_log_group(logGroupName=LOG_GROUP)
except client.exceptions.ResourceAlreadyExistsException:
pass

Flask restful - Exception handling traceback?

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.

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')

Don't send exceptions to the client

It seems that any except thrown in my WAMP server is caught by Autobahn and sent to the client. There are two problems with this: a) if something goes wrong on the server, it needs to be logged on the server so it can be fixed by the team, and b) those exceptions could potentially be very revealing (esp. exceptions from the ORM) and I don't want them to be forwarded to a malicious third party.
Can I prevent this behavior? If I can, is there a way to explicitly send the error messages that should be sent to the client?
Updated answer:
AutobahnPython no longer sends tracebacks by default.
When a procedure of a Callee is invoked that raises an exception, only the exception is forwarded - not a full traceback. To enable forwarding of tracebacks, set traceback_app = True on the Callee's ApplicationSession.
Deprecated answer: The following original answer does only apply to WAMP v1 code in AutobahnPython, which has been deprecated and removed.
The last version of AutobahnPython with WAMP1 code was 0.8.15, which you can find here and here.
You can override the onAfterCallError hook on your class deriving from autobahn.wamp.WampProtocol (see here or the generated docs).
In your override you can do your custom logging and/or modification of the error object that is the basis for the WAMP RPC error return sent to the caller.
try:
main()
except Exception, e:
log(str(e))

Categories