EDIT
This question is no longer relevant, since flask-restful new release can handle it by itself.
ORIGINAL QUESTION:
I have a flask-restful API, and I use reqparse in order to parse arguments.
Now, I want to abort the request if the user uses an unknown argument.
Using reqparse, I can abort when I detect a known argument with a bad value, but it doesn't seem to have a default case where I could treat the others.
It would prevent users to contact me with "why isn't it working ?" when they are the one who are not using correct arguments.
How would you do?
EDIT: As asked, here is an example of view:
class myView(restful.Resource):
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('arg1', type=str, action='append')
parser.add_argument('arg2', type=myType, action='append')
args = parser.parse_args()
result = dao.getResult(arg1, arg2)
return jsonify(result)
api.add_resource(myView, '/view')
What I want is this: If a user goes to ip/view?bad_arg=bad then they get a 400 error.
Beginning with 0.3.1, you can pass strict=True to reqparse.parse_args. https://github.com/flask-restful/flask-restful/pull/358
reqparse does not provide a built in solution, but I was able to get over my problem by sub classing the reqparse parser.
class StrictParser(reqparse.RequestParser):
def parse_args(self, req=None):
"""Overide reqparse parser in order to fail on unknown argument
"""
if req is None:
req = request
known_args = [arg.name for arg in self.args]
for arg in request.args:
if arg not in known_args:
bad_arg = reqparse.Argument(arg)
bad_arg.handle_validation_error("Unknown argument: %s" % arg)
return reqparse.RequestParser.parse_args(self,req)
Related
I am learning Python-Flask and found out that there are two ways to create endpoints in an application.
1. app.routing(/endpoint)
2. api.add_resource(CLASSNAME, endpoint)
Using app.routing() we can just add an endpoint over a method and invoke it. Using api.add_resource() we need to register the class name & the end points.
I've seen that the method names are given as get() & put() if you are using api.add_resource()
For ex:
app = Flask(__name__)
api = Api(app)
vehicles = []
class VehicleData(Resource):
parser = reqparse.RequestParser()
parser.add_argument('vehicle', type=str, required=True, help='name cannot be empty')
parser.add_argument('type', type=str, required=True, help='vehicle type cannot be empty')
parser.add_argument('wheels', type=int, required=True, help='number of wheels cannot be empty')
parser.add_argument('suv', type=bool, required=False, help='SUV or not can be empty')
def get(self, name):
vehicle = next(filter(lambda x: x['name'] == name, vehicles), None)
return {'vehicle': vehicle}, 200 if vehicle else 404
def post(self, name):
# data = request.get_json()
# sport.append({'sportname': data['sport_name'], 'team_size':data['team_size'], 'popularity':data['popularity']})
if next(filter(lambda x: x['name'] == name, vehicles), None) is not None:
print("in the IF BLOCK")
return {'message': 'The vehicle {n} already exists in the database'.format(n=name)}, 404
v_data = VehicleData.parser.parse_args()
vehicle = {'name': name, 'type':v_data['type'], 'vehicle': v_data['vehicle'], 'suv': v_data['suv'], 'wheels': v_data['wheels']}
vehicles.append(vehicle)
return vehicle, 201
def getallvehicles(self):
return {'vehicles': vehicles}
api.add_resource(VehicleData, '/addvehicle/<string:name>', '/getvehicle/<string:name>')
app.run(port=5000, debug=True)
If I submit the API http://127.0.0.1:5000/getvehicle with a GET http call, I am properly getting the data as per the logic given in the code. In the code I have only one GET & POST method. So Flask is automatically invoking the get() when I submit http://127.0.0.1:5000/getvehicle/<name> with a GET request.
What if I have a method name other than get()/post()/put() ?
For example, if I have a method getallvehicles() that returns all the vehicles i.e returns the list vehicles, how can I register it in api.register() ?
For example, I tried this:
api.add_resource(VehicleData, '/addvehicle/<string:name>', '/getvehicle/<string:name>', '/getallvehicles')
and submitted the API call 'http://127.0.0.1:5000/getallvehicles` with a GET request, I am facing an error:
File "/Users/bobby/PyCharmProjects/FlaskAPI/venv/lib/python3.7/site-packages/flask_restful/__init__.py", line 583, in dispatch_request
resp = meth(*args, **kwargs)
TypeError: get() missing 1 required positional argument: 'name'
Could anyone tell me what is the mistake I did here and how can I give an endpoint for getallvehicles() in the below line and map it to a GET http request:
api.add_resource(VehicleData, '/addvehicle/<string:name>', '/getvehicle/<string:name>', '/getallvehicles')
The difference between the 2 different ways is, that:
This one is "native" flask method, that you can use to wrap your functions with.
#app.routing('/endpoint')
This one is a part of the restfull_flask package and it does the things in a different way than the native flask way.
api.add_resource(CLASSNAME, endpoint)
You can do the same stuff with both ways, but if you use the rest_framework, then you should use the second method :)
For the rest of your question I am sure you will find answers in this documentation:
https://flask-restful.readthedocs.io/en/latest/
I just started to code in Flask swagger backend. When I tried to extract multiple paths and the argument parameters, an error occurs, says:
TypeError: get() missing 1 required positional argument: 'q'
But actually, my "get()" function has the positional argument 'q'. I may ask why this error occurs? That could be weird since I use
request.arg.get('q')
it works good and can returns the expected query argument.
The expected API paths is:
GET 127.0.0.1:8888/book/1?q=question20
and my code is here:
from flask import Flask, request
from flask_restplus import Resource, Api
app = Flask(__name__)
api = Api(app)
#api.route("/<string:resource>/<int:rid>")
#api.doc(params={'q': 'Your query here'})
class API(Resource):
def get(self, resource, rid, q):
return {"resource": resource,
"id": rid,
"query": q
}, 200
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8888, debug=True)
I would appreciate if someone can help me solve this problem.
You can use api.parser() to document and capture query parameters
parser = api.parser()
parser.add_argument('q', type=str, help='Resource name', location='args')
#api.route("/<string:resource>/<int:rid>")
#api.doc(parser=parser)
class API(Resource):
def get(self, resource, rid):
args = parser.parse_args()
q = args['q']
return {"resource": resource,
"id": rid,
"q":q,
}, 200
This method is marked as deprecated https://flask-restplus.readthedocs.io/en/stable/parsing.html
I have written below code endpoint to post data received from front end, I have implemented the same way for get and it works but does not work for post
parser = reqparse.RequestParser()
parser.add_argument("update", type=bool, help="Update the data set")
parser.add_argument(
"set_name", type=str, help="Name of the set you want to add
#api.route("/add_set")
class AddNewSet(Resource):
#api.doc(parser=parser)
def post(self):
""" endpoint that handles request for deploying services
:return: (int) deployed service id from database.
"""
print "Hello hellooo"
args = parser.parse_args()
print "doink"
throws error:
{ "message": "Failed to decode JSON object: No JSON object could be decoded"
}
And the text "doink" does not get printed so I gave doubt parser.parse_args() is not working as expected I believe or I am doing something wrong.
By default, the RequestParser looks for args from request.json and request.values
Plz see: https://flask-restplus.readthedocs.io/en/stable/parsing.html#argument-locations
As for your code, I haven't try your way to define args for post, I do this way
#api.expect(api.model(
'ModelName', dict(
arg1 = fields.String(required=True),
arg2 = fields.Integer,
)
))
def post(self):
return {'result': 'success'}
I also recommend to give a clear return clause, even return None.
I have a problem making the simple (non-json) arguments work with POST. Just taking the simple example from their tutorials, I can't make a unit test where the task is passing as an argument. However task is never passed in. Its none.
class TodoList(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('task', type = str)
super(TodoList, self).__init__()
def post(self):
args = self.reqparse.parse_args()
#args['task'] is None, but why?
return TODOS[args['task']], 201
Unit test:
def test_task(self):
rv = self.app.post('todos', data='task=test')
self.check_content_type(rv.headers)
resp = json.loads(rv.data)
eq_(rv.status_code, 201)
What am I missing please?
When you use 'task=test' test_client do not set application/x-www-form-urlencoded content type, because you put string to input stream. So flask can't detect form and read data from form and reqparse will return None for any values for this case.
To fix it you must set up content type or use dict {'task': 'test'} or tuple.
Also for request testing better to use client = self.app.test_client() instead app = self.app.test_client(), if you use FlaskTesting.TestCase class, then just call self.client.post.
I am building a simple web service that requires all requests to be signed. The signature hash is generated using request data including the request body. My desire is to have a middleware component that validates the request signature, responding with an error if the signature is invalid. The problem is the middleware needs to read the request body using env['wsgi.input'].read(). This advances the pointer for the request body string to the end, which makes the data inaccessible to other components further down in the chain of execution.
Is there any way to make it so env['wsgi.input'] can be read twice?
Ex:
from myapp.lib.helpers import sign_request
from urlparse import parse_qs
import json
class ValidateSignedRequestMiddleware(object):
def __init__(self, app, secret):
self._app = app
self._secret = secret
def __call__(self, environ, start_response):
auth_params = environ['HTTP_AUTHORIZATION'].split(',', 1)
timestamp = auth_params[0].split('=', 1)[1]
signature = auth_params[1].split('=', 1)[1]
expected_signature = sign_request(
environ['REQUEST_METHOD'],
environ['HTTP_HOST'],
environ['PATH_INFO'],
parse_qs(environ['QUERY_STRING']),
environ['wsgi.input'].read(),
timestamp,
self._secret
)
if signature != expected_signature:
start_response('400 Bad Request', [('Content-Type', 'application/json')])
return [json.dumps({'error': ('Invalid request signature',)})]
return self._app(environ, start_response)
You can try seeking back to the beginning, but you may find that you'll have to replace it with a StringIO containing what you just read out.
The following specification deals with that exact problem, providing explanation of the problem as well as the solution including source code and special cases to take into account:
http://wsgi.readthedocs.org/en/latest/specifications/handling_post_forms.html