Whats the best way to handle invalid parameters passed in with a GET or POST request in Flask+Python?
Let's say for the sake of argument I handle a GET request using Flask+Python that requires a parameter that needs to be an integer but the client supplies it as a value that cannot be interpreted as an integer. So, obviously, an exception will be thrown when I try to convert that parameter to an integer.
My question is should I let that exception propagate, thus letting Flask do it's default thing of returning an HTTP status code of 500 back to the client? Or should I handle it and return a proper (IMO) status code of 400 back to the client?
The first option is the easier of the two. The downside is that the resulting error isn't clear as to whose at fault here. A system admin might look at the logs and not knowing anything about Python or Flask might conclude that there's a bug in the code. However, if I return a 400 then it becomes more clear that the problem might be on the client's end.
What do most people do in these situations?
HTTP 400 seems good to me.
Returning 500 in this case is wrong. It provides no information to the client, and they will assume that the problem is with the server, not the client.
There is nothing stopping you from adding a body to the 400 response that identifies the parameter with the invalid value (or whatever the problem was). Use whatever representation the client accepts, e.g. if it's an API you might return a JSON response:
{"error": "parameter age: positive integer required"}
If you look at most of the REST APIs, they will return 400 and appropriate error message back to the client if the user sends request parameters of a different type than is expected.
So, you should go with your 2nd option.
A status code of 400 means you tell the client "hey, you've messed up, don't try that again". A status code of 500 means you tell the client "hey, I've messed up, feel free to try again later when I've fixed that bug".
In your case, you should return a 400 since the party that is at fault is the client.
Related
my company has an arcgis server, and i've been trying to geocode some address using the python requests packages.
However, as long as the input format is correct, the reponse.status_code is always"200", meaning everything is OK, even if the server didn't process the request properly.
( for example, if the batch size limit is 1000 records, and I sent an json input with 2000 records, it would still return status_code 200, but half of the records will get ignored. )
just wondering if there is a way for me to know if the server process the request properly or not?
A great spot to check is the server logs to start with. They are located in your ArcGIS server manager (https://gisserver.domain.com:6443/arcgis/manager). I would assume it would log some type of warning/info there if records were ignored, but it is not technically an error so there would be no error messages would be returned anywhere.
I doubt you'd want to do this but if you want to up your limit you can follow this technical article on how to do thathttps://support.esri.com/en/technical-article/000012383
i hope this isn't a duplicate. I searched through other entries and none seemed to address the problem i'm seeing.
We have an AWS Lambda function written in Python 3.6. The Lambda function is executed through API Gateway using the Lambda proxy integration. The function is initiated without problem and the processing executes without a problem. However, no matter what we seem to do in trying to send a response, the client receives "{"message": "Internal server error"}" I don't know that it's important, but the client for our test is Postman.
We've stripped out all of our business logic and isolated the code that does the return and we still get have the same problem.
the code is:
import json
def lambdaTest(event, context)
response = {}
dummybody = {'body':'something'}
response['statusCode'] = 200
response['body'] = dummybody
response["headers"] = {"Content-Type": "application/json"},
return json.dumps(response)
i'm sure I'm doing something wrong that's simple as I don't seem many posts about this problem. I would very much appreciate any help. Thanks.
Folks,
I figured it out... one silly problem and one thing I didn't realize about the format of the response. The silly problem was that I turned the header element into an array because I had a trailing comma (I looked at the 100 times and didn't see it). But even when I commented out setting up the header, it still had the same problem.
The aspect of the response that i didn't realize is that the body is expected to be a string. So, I had to encapsulate the JSON document I wanted to return in a string. That fixed the problem.
i hope this can help someone else
I'm using soundcloud-python https://github.com/soundcloud/soundcloud-python for Soundcloud API on Ubuntu Server 16.04.1 (installed with pip install soundcloud).
Soundcloud API Rate Limits official page https://developers.soundcloud.com/docs/api/rate-limits#play-requests says that, in case an app exceeds the API rate limits, the body of the 429 Client Error response would be a JSON object, containing some additional info.
I'm interested in getting reset_time field, to inform the user when the block will be over.
The problem is that when, for example, like rate limits is exceeded, doing response = client.put('/me/favorites/%d' % song_id) the app crashes and response is null.
How can I get the JSON response body?
Why don't you read the package's source code and find out by yourself ?
Let's see... You don't explain you got that client object but browsing the source code we can see there's a "client.py" module that defines a Client class. This class does'nt not define a put method explicitely but it defines the __getattr__ hook :
def __getattr__(self, name, **kwargs):
"""Translate an HTTP verb into a request method."""
if name not in ('get', 'post', 'put', 'head', 'delete'):
raise AttributeError
return partial(self._request, name, **kwargs)
Ok, so Client.put(...) returns a partial object wrapping Client._request, which is a quite uselessly convoluted way to define Client.put(**kwargs) as return self._request("put", **kwargs).
Now let's look at Client._request: it basically make a couple sanity checks, updates **kwargs and returns wrapped_resource(make_request(method, url, kwargs)).
Looking up the imports at the beginning of the module, we can see that make_request comes from "request.py" and wrapped_resource from "resources.py".
You mention that doing an api call while over the rate limit "crashes the application" - I assume you mean "raises an exception" (BTW please post exceptions and tracebacks when saking about such problems) - so assuming this is handled at the lower level, let's start with request.make_request. A lot of data formatting / massaging obviously and finally the interesting part: a call to response.raise_for_status(). This is a hint that we are actually delegating to the famous python-requests package, which is actually confirmed a few lines above and in the requirements file
If we read python-requests fine manual, we find out what raise_for_status does - it raises a requests.exceptions.HTTPError for client (4XX) and server (5XX) status codes.
Ok now we know what exception we have. Note that you had all those informations already in your exception and traceback, which would have saved us a lot of pain here had you posted it.
But anyway... It looks like we won't get the response content, does it ? Well, wait, we're not done yet - python-requests is a fairly well designed package, so chances are we can still rescue our response. And indeed, if we look at requests.exceptions source code, we find out that HttpError is a subclass of RequestException, and that RequestException is "Initialize(d)" with "request and response objects."
Hurray, we do have our response - in the exception. So all we have to do is catch the exception and check it's response attribute - which should contains the "additional informations".
Now please understand that this took me more than half an hour to write, but about 7 minutes to sort out without the traceback - with the traceback it would have boiled down to a mere 2 minutes, the time to go to the requests.exceptions source code and make sure it keeped the request and response. Ok I'm cheating, I'm used to read source code and I use python-requests a lot, but still: you could have solved this by yourself in less than an hour, specially with python's interactive shell which let's you explore and test live objects in real time.
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.")
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.