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.
Related
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"
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
I have a Flask application where I let users access third party applications and fetch data from them and perform some visualizations.Now the user has to provide the application name and it's credentials in order to fetch the data.Now I want to avoid putting the application name in the url and rather all of the data should be sent as a POST request where I will parse the POST data, connect to the required app with the given credentials and perform some visualizations.This is what the user will send as a POST data
{
"application_name": "appdynamics",
"account_id": "sdf632sef",
"username": "kuhku86tg",
"password": "oihsd832"
}
Now I want to trigger my particular REST API class based on the application name provided by the user.
The way I planned was to create a seperate file that involves getting the POST data using request parser and then calling it in the main application where I will trigger my REST API class with a if condition based on the application name.Below is the file parse.py
from flask_restful import reqparse
# create a parser object
parser = reqparse.RequestParser()
# add agruments to the parser object
parser.add_argument('account_id', type=str, required=False, help="Please define 'account_id'")
parser.add_argument('username', type=str, required=False, help="Please define 'username'")
parser.add_argument('password', type=str, required=False, help="Please define 'password'")
parser.add_argument('application_name', type=str, required=False, help="Please define 'application name'")
data = parser.parse_args()
Now I call it in the main application app.py
from parser import data
from flask import Flask
from flask_restful import Api
app = Flask(__name__)
# create an API for the Flask app
api = Api(app)
# if the user demands info for appdynamics, trigger the Appdynamics API class
if data['application_name'] == "appdynamics":
api.add_resource(AppdynamicsAPI, "/<string:name>") # the string will contain the metric requirement
if __name__ == "__main__":
app.run(port=5000, debug=True)
Below is the section where the logic for the REST API is written
from parser import data
from flask_restful import Resource, reqparse
from fetch_data.appdynamics import fetch_all_apps, fetch_avg_resp_time, calls_per_min
from models.user import *
class AppdynamicsAPI(Resource):
# authenticate users
def post(self, name):
first_data = data
# if the user passes the credentials, insert it into the database otherwise use the last known credentials
# ensure you only insert valid credentials
if all([first_data['account_id'], first_data['password'], first_data['username']]):
users.update(first_data, {i: j for i, j in first_data.items()}, upsert=True)
print({i: j for i, j in first_data.items()})
credentials = users.find_one({})
print("Credentials", credentials)
account_id = credentials['account_id']
username = credentials['username']
password = credentials['password']
t_duration = first_data['t_duration']
if name == "allapps":
status_code, result = fetch_all_apps(account_id, username, password)
if status_code == 200:
return {"information": result}, status_code
return {"message": "Please enter correct credentials"}, status_code
However I receive the below error
Traceback (most recent call last):
File "/home/souvik/PycharmProjects/ServiceHandler/app.py", line 3, in <module>
from resource.appdynamics_resource import AppdynamicsAPI
File "/home/souvik/PycharmProjects/ServiceHandler/resource/appdynamics_resource.py", line 4, in <module>
from authentication.parser import data
File "/home/souvik/PycharmProjects/ServiceHandler/authentication/parser.py", line 14, in <module>
data = parser.parse_args()
File "/home/souvik/utorapp/lib/python3.5/site-packages/flask_restful/reqparse.py", line 302, in parse_args
req.unparsed_arguments = dict(self.argument_class('').source(req)) if strict else {}
File "/home/souvik/utorapp/lib/python3.5/site-packages/werkzeug/local.py", line 364, in <lambda>
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
File "/home/souvik/utorapp/lib/python3.5/site-packages/werkzeug/local.py", line 306, in _get_current_object
return self.__local()
File "/home/souvik/utorapp/lib/python3.5/site-packages/flask/globals.py", line 37, in _lookup_req_object
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
You currently call data = parser.parse_args() in top-level code of a module. This runs at import time, but there is nothing to parse while your module gets imported, since this happens during startup and not while handling a request.
Call this from your view function (ie the code that runs while handling a request) instead. You will also need to restructure your code - calling api.add_resource() is something you do during startup/initialization time, not while handling a request.
The important thing to understand is that this is not PHP where all your code runs when a request is received. Instead, the Python modules are imported when you start your application (flask run, app.run(), or running it in a WSGI container). When a request is received only the code related to handling that request runs.
I found this examples to upload files to a server using Tornado Python Web framework but the thing is that none of the examples have error handling developed. If I submit the form with no file attached, it returns a 500 error. The idea is to set up the upload file field as optional not mandatory.
https://github.com/vamsiikrishna/tornado-upload
http://technobeans.wordpress.com/2012/09/17/tornado-file-uploads/
Could you please give me a hand?
I guess this may have an easy solution but I am quite a newbie.
Thanks in advance!
Googleing I found this solution:
import tornado.ioloop
import tornado.web
UPLOAD_FILE_PATH = '/path/to/files/'
class MainHandler(tornado.web.RequestHandler):
def get(self):
args = dict(username = 'visitor')
self.render('home.html', **args)
class UploadHandler(tornado.web.RequestHandler):
def post(self):
username = self.get_argument('username', 'anonymous')
if self.request.files.get('uploadfile', None):
uploadFile = self.request.files['uploadfile'][0]
filename = uploadFile['filename']
fileObj = open(UPLOAD_FILE_PATH+username+filename, 'wb')
fileObj.write(uploadFile['body'])
self.redirect('/')
application=tornado.web.Application([(r'/',MainHandler),('/upload', UploadHandler) ],
template_path = 'templates',
debug = True
)
if __name__=='__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
The think is the following condition placed before file request: if self.request.files.get('uploadfile', None):