How to create dynamic file sending algorithm with flask - python

I have some problems with file serving with flask.
Firstly, my file tree:
processedMain
--processed1
--processed2
--...
--processedN
In each folder I have bunch of files that I represent it all in table where i put href link with path of each of files and pass it through route function with send_file return.
The problem is that the only way that I was able to make it work is to creating #app.route for every subfolder hardcoded, like this:
#app.route('/' + pathFits1[1] + '<id>', methods=['GET'])
def returnImg1(id):
return send_file(pathFits1[1] + id, as_attachment=True, attachment_filename=id)
#app.route('/' + pathFits1[2] +'<id>', methods=['GET'])
def returnImg2(id):
return send_file(pathFits1[2] + id, as_attachment=True, attachment_filename=id)
#app.route('/' + pathFits1[3] + '<id>', methods=['GET'])
def returnImg3(id):
return send_file(pathFits1[3] + id, as_attachment=True, attachment_filename=id)
where pathFits1[i] is path to the subfolder and id is passed when user clicks on file in web table.
Problem is that I have a lot of subfolder, and its tiring to create #app.route for each.
Is this possible to create just one route for every thing?
P.S.
I'm quite new to flask, so if I forgot to tell something, please tell me I will fill it out.
Solution
Actually it was embarrassingly simple.
As #Dan.D pointed, flask has convertors of captured variables, for me it was all I needed, as my id variable already contains full path to file.
So now it's just this 3 lines which substitute a whole bunch or duplicated code:
#app.route('/<path:id>')
def returnImg(id):
fileName = id.rsplit('/', 1)[-1]
return send_file(id, as_attachment=True, attachment_filename=fileName)
Where id is full path to file, path: is converter to path and fileName is name of file that user will download without path in it (all after last slash)

You can capture the path in the route expression. From the documentation:
#app.route('/path/<path:subpath>')
def show_subpath(subpath):
# show the subpath after /path/
return 'Subpath %s' % subpath
From http://flask.pocoo.org/docs/1.0/quickstart/

As far as I know, Flask doesn't support multiple variable sections in #app.route URLs. However, you could set the full path of the file as a variable section and then parse it to extract the sub-folder path and the file ID.
Here's a working example:
pathFits1 = [None] * 3
pathFits1[1] = 'content/images1/'
pathFits1[2] = 'content/images2/'
#app.route('/<path:fullpath>', methods=['GET'])
def returnImg(fullpath):
global pathFits1
print("--> GET request: ", fullpath)
# parse the full path to extract the subpath and ID
# subpath: get everything until the last slash
subpath = fullpath.rsplit('/', 1)[:-1][0] + '/'
# id: get string after the last slash
id = fullpath.rsplit('/', 1)[-1]
print("ID:", id, "\tsubpath:", subpath)
# try to send the file if the subpath is valid
if subpath in pathFits1:
print("valid path, attempting to sending file")
try:
return send_file(subpath + id, as_attachment=True, attachment_filename=id)
except Exception as e:
print(e)
return "file not found"
return "invalid path"
However, a much better implementation would be to send the file ID and path as GET request parameters (eg. http://127.0.0.1:8000/returnimg?id=3&path=content/images1/) and retrieve them like this:
from flask import Flask, send_file, request
app = Flask(__name__)
pathFits1 = [None] * 3
pathFits1[1] = 'content/images1/'
pathFits1[2] = 'content/images2/'
#app.route('/returnimg', methods=['GET'])
def returnImg():
global pathFits1
id = request.args.get('id')
path = request.args.get('path')
print("ID:", id, "\tpath:", path)
# try to send the file if the subpath is valid
if path in pathFits1:
print("valid path, attempting to sending file")
try:
return send_file(path + id, as_attachment=True, attachment_filename=id)
except Exception as e:
print(e)
return "file not found"
return "invalid path"

Related

Python Flask Restful After Request Delete File

I am writing a simple PDF converter inside container. Send docx and get PDF, but I don't want file to stay on server, therefor I wish to delete them after download request.
I tried to use flask after_this_request on get request on Download(Resource)
class Downloader(Resource):
def get(self, doc_id):
folder, file_name = FileConverter.download_file(doc_id)
if not folder:
return jsonify({"status": "NOTOK", "error": "No File"})
#after_this_request
def _clean_file():
FileConverter.delete_file(doc_id)
return send_from_directory(folder, file_name, as_attachment=True)
FileConverter.delete_file checks if file exists and uses os.remove to delete it, however this part of code corrupt PDF into unreadable. If I remove #after_this_request, I get working PDF.
How should I do this?
I had this problem and I solved it like this:
class Downloader(Resource):
def get(self, doc_id):
folder, file_name = FileConverter.download_file(doc_id)
if not folder:
return jsonify({"status": "NOTOK", "error": "No File"})
try:
return send_from_directory(folder, file_name, as_attachment=True)
finally:
FileConverter.delete_file(doc_id)

How do I save a image using a flask API then return it to my React App can use it

I am trying to use my Flask API to save an image to the database OR just a file system but this is something I have never done and am getting nowhere with it.
I would like to be able to return the image back when the route is called and be able to use it in my ReactJS Application using just a img tag.
All I have been able to find is how to save the image to the Database and then download it using a route. I need to be able to return it. (It works just not what I need.)
Here is what that was:
#app.route('/img-upload', methods=['POST'])
def img_upload():
file = request.files['image']
newFile = Mealplan(name=file.filename, data=file.read())
db.session.add(newFile)
db.session.commit()
return jsonify({"Done!" : "The file has been uploaded."})
#app.route('/get-mealplan-image/<given_mealplan_id>')
def download_img(given_mealplan_id):
file_data = MealPlan.query.filter_by(id=given_mealplan_id).first()
return send_file(BytesIO(file_data.data), attachment_filename=file_data.name, as_attachment=True)
Save the files on the file system will be a more proper method. Here is a minimal example:
from flask import send_from_directory
basedir = os.path.abspath(os.path.dirname(__file__))
uploads_path = os.path.join(basedir, 'uploads') # assume you have created a uploads folder
#app.route('/img-upload', methods=['POST'])
def upload_image():
f = request.files['image']
f.save(os.path.join(uploads_path , f.filename)) # save the file into the uploads folder
newFile = Mealplan(name=f.filename) # only save the filename to database
db.session.add(newFile)
db.session.commit()
return jsonify({"Done!" : "The file has been uploaded."})
#app.route('/images/<path:filename>')
def serve_image(filename):
return send_from_directory(uploads_path, filename) # return the image
In your React app, you can use the filename to build to the image URL: /images/hello.jpg
Update:
If you can only get the id, the view function will be similar:
#app.route('/get-mealplan-image/<given_mealplan_id>')
def download_img(given_mealplan_id):
file_data = MealPlan.query.filter_by(id=given_mealplan_id).first()
return send_from_directory(uploads_path, file_data.name)

Passing a variable to a query in google-drive-api files().list function

I'm trying to create a function which takes in a directory name as a parameter and returns the ID of the directory. The ultimate goal is to list the files in said directory using the returned file ID, however I can't seem to figure out the syntax for passing the directory name in as a variable.
I can only manage to do something of the following, where I need to explicitly type in the name of the directory. Please note that I am using v3 of the google drive api.
def get_folder_id():
folder_ID = ""
while True:
response = drive_service.files().list(
q="name='folder1'", fields="nextPageToken, files(id,name)").execute()
files = response.get('files', [])
for item in files:
print ("Found file: %s, %s" % (item['name'], item['id']))
folder_ID = item['id']
page_token = response.get('nextPageToken', None)
if page_token == None:
break
return folder_ID
How can I pass the folder name as a parameter and still include it in the q query?
I've tried variants such as:
response = drive_service.files().list(
q='"name="' + "'" + f_name + "'" + '"', fields="nextPageToken, files(id,name)").execute()
In an attempt to include the single quotes around the request, but this doesn't work either
You want to retrieve the folder ID from the folder name using Drive API v3 with Python.
If my understanding is correct, how about using the following search query? Please modify as follows.
From:
q='"name="' + "'" + f_name + "'" + '"'
To:
q="name='" + f_name + "' and mimeType='application/vnd.google-apps.folder' and trashed=false"
In this modification, the search query was modified.
mimeType='application/vnd.google-apps.folder' means the folder.
trashed=false means files outside of the trash box.
By above search query, the folders which has the folder name of f_name are retrieved.
Reference:
Search for Files
If I misunderstood your question and this was not the result you want, I apologize.
Not sure if you already got the answer for your question.
However, if you want to pass the variable name as part of the query in GoogleDrive request then append the variable name with + before and after it.
year_id = "Blah Blah'
Example :- q="parents in '"+year_id+"'
Happy coding!!!

Sending a file via http in Python with bottle framework

How can I send a file as answer to an HTTP request in Python using the bottle framework?
For upload this code works for me:
#route('/upload', method='POST')
def do_upload():
category = request.forms.get('category')
upload = request.files.get('upload')
name, ext = os.path.splitext(upload.raw_filename)
if ext not in ('.png','.jpg','.jpeg'):
return 'File extension not allowed.'
save_path = category
upload.raw_filename = "hoho" + ext
upload.save(save_path) # appends upload.filename automatically
return 'OK'
Just call Bottle's static_file function and return its result:
#route('/static/<filename:path>')
def send_static(filename):
return static_file(filename, root='/path/to/static/files')
That example, when called with url /static/myfile.txt, will return the contents file /path/to/static/files/myfile.txt.
Other SO questions (like this one) contain additional examples.

Return file from python module

Edit: How to return/serve a file from a python controller (back end) over a web server, with the file_name? as suggested by #JV
You can either pass back a reference to the file itself i.e. the full path to the file. Then you can open the file or otherwise manipulate it.
Or, the more normal case is to pass back the file handle, and, use the standard read/write operations on the file handle.
It is not recommended to pass the actual data as files can be arbiterally large and the program could run out of memory.
In your case, you probably want to return a tuple containing the open file handle, the file name and any other meta data you are interested in.
Fully supported in CherryPy using
from cherrypy.lib.static import serve_file
As documented in the CherryPy docs - FileDownload:
import glob
import os.path
import cherrypy
from cherrypy.lib.static import serve_file
class Root:
def index(self, directory="."):
html = """<html><body><h2>Here are the files in the selected directory:</h2>
Up<br />
""" % os.path.dirname(os.path.abspath(directory))
for filename in glob.glob(directory + '/*'):
absPath = os.path.abspath(filename)
if os.path.isdir(absPath):
html += '' + os.path.basename(filename) + " <br />"
else:
html += '' + os.path.basename(filename) + " <br />"
html += """</body></html>"""
return html
index.exposed = True
class Download:
def index(self, filepath):
return serve_file(filepath, "application/x-download", "attachment")
index.exposed = True
if __name__ == '__main__':
root = Root()
root.download = Download()
cherrypy.quickstart(root)
For information on MIME types (which are how downloads happen), start here: Properly Configure Server MIME Types.
For information on CherryPy, look at the attributes of a Response object. You can set the content type of the response. Also, you can use tools.response_headers to set the content type.
And, of course, there's an example of File Download.

Categories