Flask: How can I send '+' symbol into a GET query parameter - python

I'm using this code to get a query parameter, that is a 10 digit number that includes a '+' in the begin (it's a phone number, i.e. +541143214321). I can get the number ok, but it comes with and empty space instead of the '+'.
This is the code:
#!/usr/bin/env python3
import json
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
app = Flask(__name__)
api = Api(app)
class Run_Test(Resource):
def get(self, enviro):
parser = reqparse.RequestParser()
parser.add_argument('phone_number', type=str, required=False)
args = parser.parse_args()
phone_number = args['phone_number']
print("phone_number: ", phone_number, flush=True)
def start():
host = '0.0.0.0'
port = 9090
app.run(host=host, port=port, debug=True)
api.add_resource(Run_Test, '/run-test/<enviro>')
The request I run is, from Postman:
GET http://localhost:9090/run-test/pre?phone_number='+541143214321'
The result I get:
phone_number: ' 541143214321'
The same with '', with "" and without them. Any suggestion?
Thanks in advance.

Related

send filename and file to python api for upload image

followings are my code for server and client where i want to pass filename in server api so that server and save the image as sender filename.
server.py
from flask import Flask
from flask_restful import Resource, Api, reqparse
import werkzeug
app = Flask(__name__)
api = Api(app)
class UploadImage(Resource):
def post(self):
parser = reqparse.RequestParser()
#parser.add_argument('FNAME', required=True)
parser.add_argument('file', type=werkzeug.datastructures.FileStorage, location='files')
args = parser.parse_args()
imageFile = args['file']
#filename = args['FNAME']
#print(filename)
imageFile.save('test.jpg')
api.add_resource(UploadImage, '/uploadimage')
if __name__ == '__main__':
#app.run() # run our Flask app
from waitress import serve
print("Running....")
serve(app,host="0.0.0.0",port=8080)
print("Stopped....")
client.py
import requests
import json
dfile = open("test.jpg", "rb")
url = "http://127.0.0.1:8080/uploadimage"
test_res = requests.post(url, files = {"file": dfile})
print(test_res)
if test_res.ok:
print(" File uploaded successfully ! ")
else:
print(" Please Upload again ! ")
The commented-out code you had would work with a small tweak, but if you're going by the standard Flask-RESTful documentation, it doesn't mention that you need to set the location on the argument.
With werkzeug-2.1.0, the flask-restful reqparse now has a 'bug' where if reqparse attempts to access an argument using the default location parameter, it will raise an exception on the server side. See this Github issue for more information.
So you need to add your filename (or FNAME or whatever) argument, but with location="form" specified like in the following Flask app that will accept an image in the file field, and a filename field to specify the name to save it as.
from flask import Flask
from flask_restful import Resource, Api, reqparse
import werkzeug
app = Flask(__name__)
api = Api(app)
upload_parser = reqparse.RequestParser(bundle_errors=True)
upload_parser.add_argument(
'file',
required=True,
type=werkzeug.datastructures.FileStorage,
location='files'
)
# note that location="form" is specified
upload_parser.add_argument(
"filename",
required=True,
type=str,
location="form"
)
class UploadImage(Resource):
def post(self):
args = upload_parser.parse_args()
image = args.file
image.save(args.filename)
api.add_resource(UploadImage, '/uploadimage')
if __name__ == '__main__':
app.run()
# you can add back your waitress.serve stuff here
# but I didn't want to bother in my test environment
And now I can hit it with a request and have it save my image:
curl http://localhost:5000/uploadimage \
-F "filename=my-image.png" \
-F "file=#/path/to/image.png"

Why is reqparse not understanding the POST request?

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

Python Flask - Request object doesn't exist even though it's imported

I have an extremely bare bones python REST application:
main.py
from app import app
def main():
parser = ArgumentParser(description="Character information hosting server!")
parser.add_argument('--parse-word-doc', dest="parse_doc", required=False, action='store_true',
help='Parses a word document for characters and then serializes them with pickle to a file')
parser.add_argument('--source-file', dest="source_file", type=str, required=False,
help='Path and name of the source word document you want to read from. Defaults to source.docx.',
default="source.docx")
parser.add_argument('--port', dest="port", required=False, type=int, default=5000,
help='Specify the port you want Flask to run on')
parser.add_argument('--log-level', metavar='LOG_LEVEL', dest="log_level", required=False, type=str, default="info",
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='The log level at which you want to run.')
args = parser.parse_args() # type: argparse.Namespace
if not Path(args.source_file).is_file():
logging.error("Could not find " + args.source_file + ". Are you sure you got the path right?")
exit(1)
logging.info("Reading data from the file \"database\" from disk")
with open('database', 'rb') as database:
characters = pickle.load(database)
app.config['DATABASE'] = characters
if args.log_level:
if args.log_level == "debug":
logging.basicConfig(level=logging.DEBUG)
app.config['DEBUG'] = True
elif args.log_level == "info":
logging.basicConfig(level=logging.INFO)
elif args.log_level == "warning":
logging.basicConfig(level=logging.WARNING)
elif args.log_level == "error":
logging.basicConfig(level=logging.ERROR)
elif args.log_level == "critical":
logging.basicConfig(level=logging.CRITICAL)
else:
logging.basicConfig(level=logging.INFO)
app.run(host='0.0.0.0', port=args.port)
if __name__ == '__main__':
main()
app/init.py
from flask import Flask
# Initialize the app
app = Flask(__name__)
# Load the views
from app import views
app/config.py
import os
class Config(object):
# Enable Flask's debugging features. Should be False in production
DEBUG = os.environ.get('DEBUG') or True
SECRET_KEY = os.environ.get('SECRET_KEY') or 'default-secret-just-for-csrf-attacks-nbd'
app/views.py
from flask import request
from app import app
#app.route('/api/lookup', methods=['GET'])
def lookup():
input_text = request.args.get('character_to_lookup')
if input_text in app.config['DATABASE']:
return app.config['DATABASE'][input_text]
else:
return {}
Problem
If I send a simple curl command: curl -X GET -d '{"character_to_lookup": "test"}' http://127.0.01:5000/api/lookup -H 'Content-Type: application/json'
and check in PyCharm for debugging - request simply doesn't exist at all. It isn't set to none, it just doesn't exist. There must be something about the app context I'm missing, but it isn't clear.
The only thing I've been able to find online is that you need to import request, which I've done and I can't think of another reason that request simply wouldn't exist.
The problem was that request.args in flask refers specifically to data passed via the url. Flask uses different values in request for different types of data. In my case, I wanted JSON so I had to use request.get_json().
See this answer for a great explanation.

Flask RESTful API: Issue with db connection pool

I'm trying to create REST API endpoints using flask framework. This is my fully working script:
from flask import Flask, jsonify
from flask_restful import Resource, Api
from flask_restful import reqparse
from sqlalchemy import create_engine
from flask.ext.httpauth import HTTPBasicAuth
from flask.ext.cors import CORS
conn_string = "mssql+pyodbc://x:x#x:1433/x?driver=SQL Server"
auth = HTTPBasicAuth()
#auth.get_password
def get_password(username):
if username == 'x':
return 'x'
return None
app = Flask(__name__)
cors = CORS(app)
api = Api(app)
class Report(Resource):
decorators = [auth.login_required]
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('start', type = str)
parser.add_argument('end', type = str)
args = parser.parse_args()
e = create_engine(conn_string)
conn = e.connect()
stat = """
select x from report
"""
query = conn.execute(stat)
json_dict = []
for i in query.cursor.fetchall():
res = {'x': i[0], 'xx': i[1]}
json_dict.append(res)
conn.close()
e.dispose()
return jsonify(results=json_dict)
api.add_resource(Report, '/report')
if __name__ == '__main__':
app.run(host='0.0.0.0')
The issue is that I get results when I call this API only for a day or so after which I stop getting results unless I restart my script (or sometimes even my VM) after which I get results again. I reckon there is some issue with the database connection pool or something but I'm closing the connection and disposing it as well. I have no idea why the API gives me results only for some time being because of which I have to restart my VM every single day. Any ideas?
Per my experience, the issue was caused by coding create_engine(conn_string) to create db pool inside the Class Report so that always do the create & destory operations of db pool for per restful request. It's not correct way for using SQLAlchemy ORM, and be cause IO resouce clash related to DB connection, see the engine.dispose() function description below at http://docs.sqlalchemy.org/en/rel_1_0/core/connections.html#sqlalchemy.engine.Engine:
To resolve the issue, you just need to move e = create_engine(conn_string) to the below of the code conn_string = "mssql+pyodbc://x:x#x:1433/x?driver=SQL Server" and remove the code e.dispose() both in the Class Report, see below.
conn_string = "mssql+pyodbc://x:x#x:1433/x?driver=SQL Server"
e = create_engine(conn_string) # To here
In the def get(delf) function:
args = parser.parse_args()
# Move: e = create_engine(conn_string)
conn = e.connect()
and
conn.close()
# Remove: e.dispose()
return jsonify(results=json_dict)

flask-restful: how to parse parameters when connected to sql server db?

I want to create a REST API using python flask. So anytime I do this:
localhost/customers?cust_country=USA
I want to fetch every row from table 'customers' where everyone is from USA.
This is the script I've so far:
from flask import Flask, request
from flask_restful import Resource, Api
from sqlalchemy import create_engine
from flask_restful import reqparse
e = create_engine("mssql+pyodbc://....")
app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('cust_country', type = 'string')
class Dep(Resource):
def get(self):
conn = e.connect()
args = parser.parse_args()
query = conn.execute("select * from customers where cust_country = ?", [args['cust_country']])
print(query)
return {'custid': [i[0] for i in query.cursor.fetchall()]}
api.add_resource(Dep, '/customers')
if __name__ == '__main__':
app.run()
I'm getting this error:
C:\Users\x>curl 127.0.0.1:5000/customers?cust_country=USA 404 Not
Found Not Found The requested URL was not found on
the server. If you entered the URL manually please check your
spelling and try again.
======================
part 2: sending multiple parameters:
from flask import Flask
from flask_restful import Resource, Api
from flask_restful import reqparse
from sqlalchemy import create_engine
e = create_engine("x")
parser = reqparse.RequestParser()
parser.add_argument('cust_country', type = str)
parser.add_argument('cust_name', type = str)
app = Flask(__name__)
api = Api(app)
class Dep(Resource):
def get(self):
args = parser.parse_args()
conn = e.connect()
query = conn.execute("select cust_id from customers where cust_country = ? and cust_name = ?", [args['cust_country'], args['cust_name']])
return {'custid': [i[0] for i in query.cursor.fetchall()]}
api.add_resource(Dep, '/customers')
if __name__ == '__main__':
app.run()
this is what I'm using in my curl:
curl "127.0.0.1:5000/customers?cust_country=USA&cust_name=Wascals"
One error that you may be facing is by using
parser.add_argument('cust_country', type = 'string') which is wrong.
It should be parser.add_argument('cust_country', type = str).
Also I would suggest to use app.run(debug=True) as it will help you debug simple errors.

Categories

Resources