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
Related
Hi we are facing an issue with python flask running on Kubernetes(EKS), we never faced this issue while running the same app on EC2 servers
The issue is while reading the incoming request payload. it is not re-producible and it happens once in 100 requests, sometimes it takes time ~1 to 5 sec, some times it is throwing an error at 20s, mentioned the error below
builtins:OSError cannot read from timed out object
Below is the code we are using
from flask import request, jsonify
from werkzeug.datastructures import ImmutableMultiDict
def request_parser():
'''
Request Parser will parse args and kwargs from request object
where args are ImmutableMultiDict, convert to_dict with flat=False,
and return only dict type
'''
try:
if request.method == "GET":
args = request.args
elif request.method in ("POST", "PUT"):
args = request.json or request.form
elif request.method == "DELETE":
args = request.args
if request.get_json(silent=True):
request.json.update(request.args.to_dict(flat=False))
args = request.json
else:
args = request.args
# Checking if the args is immutablemultidict
# if it then it is required to change to dict
# because of change in python3.
if isinstance(args, ImmutableMultiDict):
args = args.to_dict(flat=False)
return args
except Exception as e:
return {}
from the tracing, we noticed that all the requests that are taking time are being stuck at the line args = request.json or request.form, sometimes it takes more time, some times(very rarely) it times out with above-mentioned exception in the flask Kubernetes environment, if anyone has faced a similar issue please share your inputs.
We are running on EKS cluster version 1.23.x, latest control plane components.
Nodegroups : c6i.xlarge
Traffic flow : ALB -> Edge-stack-ingress -> pods
From what I've seen online, I can use reqparse from flask_restful to add data to my GET and POST requests.
Here is what I have:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
import pandas as pd
import ast
app = Flask(__name__)
api = Api(app)
class User(Resource):
def get(self):
return {'data': 'get'}, 200
def post(self):
parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument('userId', type=str)
args = parser.parse_args()
print(args)
return {'data': 'post'}, 200
class Game(Resource):
pass
api.add_resource(User, '/user')
api.add_resource(Game, '/game')
if __name__ == '__main__':
app.run()
I'm trying to send this POST request (using Postman):
http://127.0.0.1:5000/user?userId=hello
But I always get this error back:
{
"message": "The browser (or proxy) sent a request that this server could not understand."
}
I truly don't know what I'm doing wrong...
I still have not figured out how to fix reqparse. However, I managed to get the result desired another way.
Basically you can change the api.add_resource() to include variables which you can pass into the class path. Like: api.add_resource(User, 'user/<userID>') allows you to send a request like http://127.0.0.1:5000/user/kayer and have the variable userID be kayer.
The other way (to go back to my original question on how to use this type of request: http://127.0.0.1:5000/user?userID=kayer is to implement reuest.args.get('userID') and assign the return of that function to a variable.
Full code:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
import pandas as pd
import ast
app = Flask(__name__)
api = Api(app)
class User(Resource):
def get(self, userId):
a = request.args.get('name')
return {'data': {'a': userId, 'b': a}}, 200
def post(self):
pass
class Game(Resource):
pass
api.add_resource(User, '/user/<userId>')
api.add_resource(Game, '/game')
if __name__ == '__main__':
app.run()
I think the problem is that reqparse (or flask_restful itself), by default, is not parsing the query string (?userId=hello), when it matches the request URL with the endpoint.
From https://flask-restful.readthedocs.io/en/latest/reqparse.html#argument-locations:
By default, the RequestParser tries to parse values from flask.Request.values, and flask.Request.json.
Use the location argument to add_argument() to specify alternate locations to pull the values from. Any variable on the flask.Request can be used.
As instructed and from the example on that documentation, you can explicitly specify a location keyword param as location="args", where args refers to flask.Request.args, which means the:
The parsed URL parameters (the part in the URL after the question mark).
...which is exactly where userId is set when you call the endpoint.
It seems to work after adding the location="args" parameter:
def post(self):
parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument("userId", type=str, location="args") # <--------
args = parser.parse_args()
print(args)
return {"data": args}, 200
Postman:
cURL:
$ curl -XPOST http://localhost:5000/user?userId=hello
{
"data": {
"userId": "hello"
}
}
Tested with:
Flask 2.1.2
Flask-RESTful 0.3.9
In summary, I have been following the flask restx tutorials to make an api, however none of my endpoints appear on the swagger page ("No operations defined in spec!") and I just get 404 whenever I call them
I created my api mainly following this https://flask-restx.readthedocs.io/en/latest/scaling.html
I'm using python 3.8.3 for reference.
A cut down example of what I'm doing is as follows.
My question in short is, what am I missing?
Currently drawing blank on why this doesn't work.
Directory Structure
project/
- __init__.py
- views/
- __init__.py
- test.py
manage.py
requirements.txt
File Contents
requirements.txt
Flask-RESTX==0.2.0
Flask-Script==2.0.6
manage.py
from flask_script import Manager
from project import app
manager = Manager(app)
if __name__ == '__main__':
manager.run()
project/init.py
from flask import Flask
from project.views import api
app = Flask(__name__)
api.init_app(app)
project/views/init.py
from flask_restx import Api, Namespace, fields
api = Api(
title='TEST API',
version='1.0',
description='Testing Flask-RestX API.'
)
# Namespaces
ns_test = Namespace('test', description='a test namespace')
# Models
custom_greeting_model = ns_test.model('Custom', {
'greeting': fields.String(required=True),
})
# Add namespaces
api.add_namespace(ns_test)
project/views/test.py
from flask_restx import Resource
from project.views import ns_test, custom_greeting_model
custom_greetings = list()
#ns_test.route('/')
class Hello(Resource):
#ns_test.doc('say_hello')
def get(self):
return 'hello', 200
#ns_test.route('/custom')
class Custom(Resource):
#ns_test.doc('custom_hello')
#ns_test.expect(custom_greeting_model)
#ns_test.marshal_with(custom_greeting_model)
def post(self, **kwargs):
custom_greetings.append(greeting)
pos = len(custom_greetings) - 1
return [{'id': pos, 'greeting': greeting}], 200
How I'm Testing & What I Expect
So going to the swagger page, I expect the 2 endpoints defined to be there, but I just see the aforementioned error.
Just using Ipython in a shell, I've tried to following calls using requests and just get back 404s.
import json
import requests as r
base_url = 'http://127.0.0.1:5000/'
response = r.get(base_url + 'api/test')
response
response = r.get(base_url + 'api/test/')
response
data = json.dumps({'greeting': 'hi'})
response = r.post(base_url + 'test/custom', data=data)
response
data = json.dumps({'greeting': 'hi'})
response = r.post(base_url + 'test/custom/', data=data)
response
TL;DR
I made a few mistakes in my code and test:
Registering api before declaring the routes.
Making a wierd assumption about how the arguments would be passed to the post method.
Using a model instead of request parser in the expect decorator
Calling the endpoints in my testing with an erroneous api/ prefix.
In Full
I believe it's because I registered the namespace on the api before declaring any routes.
My understanding is when the api is registered on the app, the swagger documentation and routes on the app are setup at that point. Thus any routes defined on the api after this are not recognised. I think this because when I declared the namespace in the views/test.py file (also the model to avoid circular referencing between this file and views/__init__.py), the swagger documentation had the routes defined and my tests worked (after I corrected them).
There were some more mistakes in my app and my tests, which were
Further Mistake 1
In my app, in the views/test.py file, I made a silly assumption that a variable would be made of the expected parameter (that I would just magically have greeting as some non-local variable). Looking at the documentation, I learnt about the RequestParser, and that I needed to declare one like so
from flask_restx import reqparse
# Parser
custom_greeting_parser = reqparse.RequestParser()
custom_greeting_parser.add_argument('greeting', required=True, location='json')
and use this in the expect decorator. I could then retrieve a dictionary of the parameters in my post method. with the below
...
def post(self):
args = custom_greeting_parser.parse_args()
greeting = args['greeting']
...
The **kwargs turned out to be unnecessary.
Further Mistake 2
In my tests, I was calling the endpoint api/test, which was incorrect, it was just test. The corrected test for this endpoint is
Corrected test for test endpoint
import json
import requests as r
base_url = 'http://127.0.0.1:5000/'
response = r.get(base_url + 'test')
print(response)
print(json.loads(response.content.decode()))
Further Mistake 3
The test for the other endpoint, the post, I needed to include a header declaring the content type so that the parser would "see" the parameters, because I had specified the location explictily as json. Corrected test below.
Corrected test for test/custom endpoint
import json
import requests as r
base_url = 'http://127.0.0.1:5000/'
data = json.dumps({'greeting': 'hi'})
headers = {'content-type': 'application/json'}
response = r.post(base_url + 'test/custom', data=data, headers=headers)
print(response)
print(json.loads(response.content.decode()))
Corrected Code
For the files with incorrect code.
views/init.py
from flask_restx import Api
from project.views.test import ns_test
api = Api(
title='TEST API',
version='1.0',
description='Testing Flask-RestX API.'
)
# Add namespaces
api.add_namespace(ns_test)
views/test.py
from flask_restx import Resource, Namespace, fields, reqparse
# Namespace
ns_test = Namespace('test', description='a test namespace')
# Models
custom_greeting_model = ns_test.model('Custom', {
'greeting': fields.String(required=True),
'id': fields.Integer(required=True),
})
# Parser
custom_greeting_parser = reqparse.RequestParser()
custom_greeting_parser.add_argument('greeting', required=True, location='json')
custom_greetings = list()
#ns_test.route('/')
class Hello(Resource):
#ns_test.doc('say_hello')
def get(self):
return 'hello', 200
#ns_test.route('/custom')
class Custom(Resource):
#ns_test.doc('custom_hello')
#ns_test.expect(custom_greeting_parser)
#ns_test.marshal_with(custom_greeting_model)
def post(self):
args = custom_greeting_parser.parse_args()
greeting = args['greeting']
custom_greetings.append(greeting)
pos = len(custom_greetings) - 1
return [{'id': pos, 'greeting': greeting}], 200
Consider the following:
from flask import Flask
from flask_restplus import Api, Resource, fields
app = Flask(__name__)
api = Api(app)
ns = api.namespace('ns')
payload = api.model('Payload', {
'a_str': fields.String(required=True),
'a_date': fields.Date(required=True)
})
#ns.route('/')
class AResource(Resource):
#ns.expect(payload)
def post(self):
pass
If I POST {"a_str": 0, "a_date": "2000-01-01"} I get 400 as expected,
because a_str is not a string.
However, when I POST {"a_str": "str", "a_date": "asd"} I don't get 400.
Here I would like to also get 400, because "asd" is not a common date format.
I looked into the Date class doc and
I see that there is a format and a parse method which should check whether the string is in a common date format.
However, they do not seem to be called here.
Is there another way how to do this?
Currently I am validating the date format by hand, but it seems that fask restplus should be able to do it for me.
As #andilabs mentions, it really weird to define expected payload twice. You can define expected payload by using only RequestParser as so:
from flask import Flask, jsonify
from flask_restplus import Api, Resource, fields, reqparse, inputs
app = Flask(__name__)
api = Api(app)
ns = api.namespace('ns')
parser = reqparse.RequestParser()
parser.add_argument('a_str', type=str)
parser.add_argument('a_date', type=inputs.datetime_from_iso8601, required=True)
#ns.route('/')
class AResource(Resource):
#ns.expect(parser)
def get(self):
try: # Will raise an error if date can't be parsed.
args = parser.parse_args() # type `dict`
return jsonify(args)
except: # `a_date` wasn't provided or it failed to parse arguments.
return {}, 400
if __name__ == '__main__':
app.run(debug=True)
Test by using curl:
$ curl -XGET -H "Content-type: application/json" -d '{"a_str": "Name", "a_date": "2012-01-01"}' 'http://127.0.0.1:5000/ns/'
{
"a_date": "Sun, 01 Jan 2012 00:00:00 GMT",
"a_str": "Name"
}
To validate you can add parameter validate:
#ns.expect(payload, validate=True)
Here is the link to documentation:
https://flask-restplus.readthedocs.io/en/stable/swagger.html#the-api-expect-decorator
Step 1: pip install isodate
Step 2: pip install strict-rfc3339
Step 3:
from jsonschema import FormatChecker
api = Api(your_app,format_checker=FormatChecker(formats=("date-time",)))
Step 4:
#api.expect(your_fields, validate=True)
Reference:
Open Issue: https://github.com/noirbizarre/flask-restplus/issues/204
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.