I am making an API for my web2py app and for the life of me cannot get it to work as shown in the web2pybook:
http://www.web2py.com/books/default/chapter/29/10/services#Low-level-API-and-other-recipes
Here is api from the defaults.py:
#request.restful()
def api():
response.view = 'generic.json'
def GET(app_listings,id):
if not app_listings=='app': raise HTTP(400)
return dict(app = db.app_listings(id))
def POST(app_user,device):
if not app_user=='usr': raise HTTP(400)
return db.usr.validate_and_insert(device)
return locals()
And here is my url for testing the API:
http://127.0.0.1:8000/my_app/default/api/usr/3/
My responses are:
400 BAD REQUEST
or if I remove the parameter on the end of the URL:
invalid arguments
Suggestions?
In your URL, you are passing "usr" and "3" as the two arguments to the GET() function, and that function includes the following line:
if not app_listings=='app': raise HTTP(400)
Since "usr" != "app", it raises the HTTP(400) exception.
Related
I am trying to parse "#" symbol as a direct url in Flask project. The issue is everytime the url is requested, it breaks any value that has # init as it's a special character in url encoding.
localhost:9999/match/keys?source=#123&destination=#123
In flask, I am trying to get these arguments like this
app.route(f'/match/keys/source=<string:start>/destination=<string:end>', methods=['GET'])
The url response that i see on console is this:
"GET /match/keys/source=' HTTP/1.0" 404 -] happens
I believe you might not fully understand how 'query strings' work in flask. This url:
app.route(f'/match/keys/source=<string:start>/destination=<string:end>', methods=['GET'])
won't work as you expect as it won't match the request:
localhost:9999/match/keys?source=#123&destination=#123
rather it aught to be:
#app.route('/match/keys', methods=['GET'])
and this would match:
localhost:9999/match/keys?source=%23123&destination=%23123
Then to catch those 'query strings' you do:
source = request.args.get('source') # <- name the variable what you may
destination = request.args.get('destination') # <- same as the naming format above
So when you call localhost:9999/match/keys?source=%23123&destination=%23123 you test for those 'query strings' in the request url and if they are they that route function would execute.
I wrote this test:
def test_query_string(self):
with app.test_client() as c:
rc = c.get('/match/keys?source=%23123') # <- Note use of the '%23' to represent '#'
print('Status code: {}'.format(rc.status_code))
print(rc.data)
assert rc.status_code == 200
assert 'source' in request.args
assert rc.data.decode('utf-8') == "#123"
and it passes using this route function:
#app.route('/match/keys', methods=['GET'])
def some_route():
s = request.args.get('source')
return s
So you see I was able to catch the query string source value in my unit test.
I found another trick to work around with it. Instead of using GET method, I switched to POST
localhost:9999/match/keys
and in the app.routes, i sent the argument to get_json.
app.route('/match/keys/',method=['POST'])
def my_func():
arg = request.get_json
In postman, I send the POST request and send the body to be like this:
Postman Post request
For returning a 400/500 response to clients in a flask webapp, I've seen the following conventions:
Abort
import flask
def index(arg):
return flask.abort("Invalid request", 400)
Tuple
def index(arg):
return ("Invalid request", 400)
Response
import flask
def index(arg):
return flask.Response("Invalid request", 400)
What are the difference and when would one be preferred?
Related question
Coming from Java/Spring, I am used to defining a custom exception with a status code associated with it and then anytime the application throws that exception, a response with that status code is automatically returned to the user (instead of having to explicitly catch it and return a response as shown above). Is this possible in flask? This is my little wrapped attempt
from flask import Response
class FooException(Exception):
""" Binds optional status code and encapsulates returing Response when error is caught """
def __init__(self, *args, **kwargs):
code = kwargs.pop('code', 400)
Exception.__init__(self)
self.code = code
def as_http_error(self):
return Response(str(self), self.code)
Then to use
try:
something()
catch FooException as ex:
return ex.as_http_error()
The best practice is to create your custom exception classes and then registering with Flask app through error handler decorator. You can raise a custom exception from business logic and then allow the Flask Error Handler to handle any of custom defined exceptions. (Similar way it's done in Spring as well.)
You can use the decorator like below and register your custom exception.
#app.errorhandler(FooException)
def handle_foo_exception(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response
You can read more about it here Implementing API Exceptions
According to official doc, you can make batch request through Google API Client Libraries for Python. Here, there is an example
from apiclient.http import BatchHttpRequest
def list_animals(request_id, response, exception):
if exception is not None:
# Do something with the exception
pass
else:
# Do something with the response
pass
def list_farmers(request_id, response):
"""Do something with the farmers list response."""
pass
service = build('farm', 'v2')
batch = service.new_batch_http_request()
batch.add(service.animals().list(), callback=list_animals)
batch.add(service.farmers().list(), callback=list_farmers)
batch.execute(http=http)
Is there a way to access to the request in the callback. E.g., print the request (not the request_id) if there is an exception?
I ran into this same issue, as I needed the original request for using compute.instances().aggregatedList_next() function.
I ended up passing a unique request_id to batch.add() and created a dictionary to keep track of the original requests that were made. Then within the callback, I referenced that dictionary and pulled out the request by using the unique request_id.
So I did something along the following:
requests = {}
project = 'example-project'
batch = service.new_batch_http_request(callback=callback_callable)
new_request = compute.instances().aggregatedList(project=project)
requests[project] = new_request
batch.add(new_request, request_id=project)
Within the callable function:
def callback_callable(self, request_id, response, exception):
original_request = requests[request_id]
Note that the value passed to the request_id needs to be a string. If an int is passed it will raise a TypeError quote_from_bytes() expected bytes.
Hope this helps someone!
In case it helps anyone - I'm using google-api-python-client==2.65.0
and the call back is passed differently - it is specified like this:
def list_animals(request_id, response, exception=None):
if exception is None:
original_request = requests[request_id]
# process here...
batch = service.new_batch_http_request(callback=list_animals)
batch.add(service.animals().list())
batch.add(service.farmers().list())
batch.execute()
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))
I'm working on a decorator that validates an api token, my first being to get the decorator in some minimal working order:
def check_token(view_method):
#wraps(view_method)
def wrapped_view(*args, **kwargs):
token = request.args['token']
if token is None:
abort(403)
return view_method(*args, **kwargs)
return wrapped_view
This sort of works, i.e. the view works if the token is provided, but if not gives a 400 Bad Request error. I want to send a 403 error, but am not aware enough to catch this yet.
What is wrong with this that it only return a 400 error? How do I improve this?
request.args is a MultiDict and it raises if a key is requested which is not there in dict. Here request.args['token'] raises before it reaches to abort(403). Few solutions are:
## If a 2nd optional argument is provided to dict.get then
## it does not raise if key is not found. Instead returns
## the 2nd argument passed as default.
token = request.args.get('token', None)
if not token:
## Error handling
Or:
if 'token' not in request.args or not request.args['token']
## Error handling