send filename and file to python api for upload image - python

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"

Related

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: How can I send '+' symbol into a GET query parameter

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.

Python restplus API to upload and dowload files

With python flask_restplus what is correct way to have a post and get methods to get and push a file e.g. xlsx to the server ?
Does the marshaling need to be used for this ?
reference: https://philsturgeon.uk/api/2016/01/04/http-rest-api-file-uploads/
This answer give general info but not in the python>flask>restplus context: REST API File Upload
First you need to configure a parser
# parsers.py
import werkzeug
from flask_restplus import reqparse
file_upload = reqparse.RequestParser()
file_upload.add_argument('xls_file',
type=werkzeug.datastructures.FileStorage,
location='files',
required=True,
help='XLS file')
Then add a new resource to your api namespace
# api.py
import …
import parsers
#api.route('/upload/')
class my_file_upload(Resource):
#api.expect(parsers.file_upload)
def post(self):
args = parsers.file_upload.parse_args()
if args['xls_file'].mimetype == 'application/xls':
destination = os.path.join(current_app.config.get('DATA_FOLDER'), 'medias/')
if not os.path.exists(destination):
os.makedirs(destination)
xls_file = '%s%s' % (destination, 'custom_file_name.xls')
args['xls_file'].save(xls_file)
else:
abort(404)
return {'status': 'Done'}
I hope this helps.

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